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 possono comunicare con i dispositivi Bluetooth nelle vicinanze in modo sicuro e nel rispetto della privacy? In questo modo, i cardiofrequenzimetri, le lampadine canterine e persino le tartarughe potrebbero interagire direttamente con un sito web.

Finora, la possibilità di interagire con i dispositivi Bluetooth è stata possibile solo per le app specifiche della piattaforma. L'API Web Bluetooth mira a cambiare questa situazione e a portarla anche nei browser web.

Prima di cominciare

Questo documento presuppone che tu abbia una conoscenza di base del funzionamento di Bluetooth Low Energy (BLE) e del profilo attributo generico.

Anche se la specifica dell'API Web Bluetooth non è ancora definitiva, gli autori della specifica sono alla ricerca di sviluppatori entusiasti che provino questa API e forniscano feedback sulla specifica 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 caratteristiche Bluetooth, ricevere notifiche GATT, sapere quando un dispositivo Bluetooth viene disconnesso e persino leggere e scrivere descrittori Bluetooth. Per saperne di più, consulta la tabella Compatibilità del browser di MDN.

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

Disponibile per le prove dell'origine

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

La prova si è conclusa correttamente nel gennaio 2017.

Requisiti di sicurezza

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

Solo HTTPS

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

Azione dell'utente richiesta

Come funzionalità di sicurezza, la rilevazione dei dispositivi Bluetooth con navigator.bluetooth.requestDevice deve essere attivata da un gesto dell'utente, ad esempio un tocco o un clic del mouse. Ci riferiamo all'ascolto degli eventi pointerup, click e touchend.

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

Scopri il codice

L'API Web Bluetooth si basa molto sulle promesse JavaScript. Se non hai familiarità con le promesse, consulta questo fantastico tutorial sulle promesse. Un'altra cosa: () => {} sono le funzioni freccia ECMAScript 2015.

Richiedere dispositivi Bluetooth

Questa versione della specifica dell'API Web Bluetooth consente ai siti web, in esecuzione nel ruolo centrale, 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 in un selettore, in cui può selezionare un dispositivo o annullare la richiesta.

Prompt per l'utente del 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 GATT Bluetooth pubblicizzati e/o al nome del dispositivo.

Filtro servizi

Ad esempio, per richiedere la pubblicità dei 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 GATT Bluetooth non è presente nell'elenco dei servizi GATT Bluetooth standardizzati, puoi fornire l'UUID Bluetooth completo o una forma breve 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 per nome

Puoi anche richiedere dispositivi Bluetooth in base al nome del dispositivo pubblicizzato con il tasto dei filtri name o anche un prefisso di questo nome con il tasto 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 dei servizi. In caso contrario, riceverai un errore in un secondo momento quando tenterai di accedervi.

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

Filtro dati produttore

È anche possibile richiedere dispositivi Bluetooth in base ai dati specifici del produttore pubblicizzati con la chiave dei filtri manufacturerData. Questa chiave è un array di oggetti con una chiave Identificatore azienda Bluetooth obbligatoria denominata companyIdentifier. Puoi anche fornire un prefisso dei dati che filtra i dati del produttore dai dispositivi Bluetooth che iniziano con questo prefisso. Tieni presente che devi anche definire la chiave optionalServices per poter accedere a qualsiasi servizio non incluso in un filtro dei servizi. In caso contrario, riceverai un errore in un secondo momento quando tenterai di accedervi.

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

Una maschera può essere utilizzata anche con un prefisso dei dati per trovare corrispondenze con alcuni pattern nei dati del produttore. Consulta la spiegazione dei filtri dei dati Bluetooth per saperne di più.

Filtri di esclusione

L'opzione exclusionFilters in navigator.bluetooth.requestDevice() ti consente di escludere alcuni dispositivi dal selettore del browser. Può essere utilizzato per escludere i dispositivi che corrispondono a un filtro più ampio, ma 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 usare il tasto acceptAllDevices per mostrare tutti i dispositivi Bluetooth nelle vicinanze. Per accedere ad alcuni servizi, devi anche definire la chiave optionalServices. In caso contrario, riceverai un errore in un secondo momento quando tenterai di accedervi.

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

Quindi, cosa fai ora che hai un BluetoothDevice? Connettiamoci al server GATT remoto Bluetooth che contiene le definizioni di servizio e caratteristica.

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 il livello di carica attuale della batteria del dispositivo.

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

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 utilizzi una caratteristica GATT Bluetooth personalizzata, puoi fornire l'UUID Bluetooth completo o una forma breve a 16 o 32 bit per service.getCharacteristic.

Tieni presente che puoi anche aggiungere un listener di eventi characteristicvaluechanged a una caratteristica per gestire la lettura del relativo valore. Dai un'occhiata all'esempio di valore della caratteristica di lettura modificato per scoprire come gestire facoltativamente anche le prossime notifiche GATT.


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

Scrivere in una caratteristica Bluetooth

Scrivere in 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 cardiofrequenzimetro.

Ti prometto che non c'è magia qui. Tutto è spiegato nella pagina Caratteristica punto controllo battito cardiaco.

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

Ricevere notifiche GATT

Ora vediamo come ricevere una notifica quando la caratteristica 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 notifiche mostra come interrompere le notifiche con stopNotifications() e rimuovere correttamente il listener di eventi characteristicvaluechanged aggiunto.

Disconnettersi da un dispositivo Bluetooth

Per offrire un'esperienza utente migliore, 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 l'app web dal dispositivo Bluetooth. Verranno attivati i listener di eventi gattserverdisconnected esistenti. Tieni presente che la comunicazione con il dispositivo Bluetooth NON verrà interrotta se un'altra app sta già comunicando con il dispositivo Bluetooth. Per approfondire, consulta l'esempio di disconnessione del dispositivo e l'esempio di riconnessione automatica.

Lettura e scrittura dei descrittori Bluetooth

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

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

Nell'esempio seguente, health_thermometer è il servizio Termometro per la salute, measurement_interval la caratteristica Intervallo di misurazione e gatt.characteristic_user_description il descrittore Descrizione utente 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 per la 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 Web Bluetooth riportati di seguito sono stati testati correttamente. Per sfruttare al meglio questi esempi, ti consiglio di installare l'app per Android [BLE Peripheral Simulator Android App], che simula un dispositivo periferico BLE con un servizio batteria, un servizio frequenza cardiaca o un servizio termometro per la salute.

Principiante

  • Informazioni dispositivo: recupera le informazioni di base del dispositivo da un dispositivo BLE.
  • Livello batteria: recupera le informazioni sulla batteria da un dispositivo BLE che pubblicizza le informazioni sulla batteria.
  • Reimposta energia: reimposta l'energia spesa da un dispositivo BLE che pubblicizza la frequenza cardiaca.
  • Proprietà della caratteristica: mostra tutte le proprietà di una caratteristica specifica di un dispositivo BLE.
  • Notifiche: avvia e interrompi le notifiche delle caratteristiche da un dispositivo BLE.
  • Disconnessione dispositivo: disconnettiti e ricevi una notifica di disconnessione di un dispositivo BLE dopo la connessione.
  • Get Characteristics (Ottieni caratteristiche): ottieni tutte le caratteristiche di un servizio pubblicizzato da un dispositivo BLE.
  • Get Descriptors (Ottieni descrittori): ottieni tutti i descrittori delle 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 corrisponde ai dati del produttore.
  • Filtri di esclusione: recupera le informazioni di base sul dispositivo da un dispositivo BLE con filtri di esclusione di base.

Combinare più operazioni

Dai un'occhiata anche alle nostre demo Web Bluetooth selezionate e ai Codelab Web Bluetooth ufficiali.

Biblioteche

  • web-bluetooth-utils è un modulo npm che aggiunge alcune funzioni pratiche all'API.
  • Un shim dell'API Web Bluetooth è disponibile in noble, il modulo centrale Node.js BLE più popolare. In questo modo puoi utilizzare webpack/browserify noble senza la necessità 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

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

Suggerimenti

In Chrome è disponibile una pagina Bluetooth Internals all'indirizzo about://bluetooth-internals che ti consente di ispezionare tutto ciò che riguarda i dispositivi Bluetooth nelle vicinanze: stato, servizi, caratteristiche e descrittori.

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

Ti consiglio inoltre di consultare la pagina ufficiale How to file Web Bluetooth bugs, dato che il debug del Bluetooth a volte 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 in fase di implementazione.

Anche se è ancora incompleto, ecco un'anteprima di ciò che ti aspetta nel prossimo futuro:

  • La ricerca di annunci BLE nelle vicinanze avverrà con navigator.bluetooth.requestLEScan().
  • Un nuovo evento serviceadded monitorerà i servizi GATT Bluetooth appena scoperti, mentre l'evento serviceremoved monitorerà quelli rimossi. Un nuovo evento servicechanged si attiverà quando una caratteristica e/o un descrittore viene aggiunto o rimosso da un servizio GATT Bluetooth.

Mostra il tuo sostegno all'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 sia fondamentale supportarle.

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

Risorse

Ringraziamenti

Grazie a Kayce Basques per la revisione.