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, cardiofrequenzimetri, lampadine che cantano e persino tartarughe potrebbero interagire direttamente con un sito web.

Finora, la possibilità di interagire con i dispositivi Bluetooth era possibile solo per le app specifiche delle piattaforme. L'API Web Bluetooth mira a cambiare questa situazione e la porta anche ai browser web.

Prima di cominciare

Questo documento presuppone che tu abbia alcune conoscenze di base sul funzionamento del Bluetooth Low Energy (BLE) e del profilo degli attributi generici.

Anche se la specifica dell'API Web Bluetooth non è ancora stata finalizzata, 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/write caratteristiche Bluetooth, ricevere notifiche GATT, sapere quando un dispositivo Bluetooth si disconnette 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 di origine per ChromeOS, Android e Mac.

La prova è terminata correttamente a gennaio 2017.

Requisiti di sicurezza

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

Solo HTTPS

Poiché questa API sperimentale è una nuova funzionalità potente aggiunta al web, viene messa a disposizione solo per i contesti sicuri. Ciò significa che dovrai eseguire la compilazione tenendo conto di TLS.

Gesto dell'utente richiesto

Come funzionalità di sicurezza, la ricerca di dispositivi Bluetooth con navigator.bluetooth.requestDevice deve essere attivata da un gesto dell'utente come un tocco o un clic del mouse. Stiamo parlando dell'ascolto degli eventi pointerup, click e touchend.

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

Esplorare il codice

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

Richiedi dispositivi Bluetooth

Questa versione della specifica dell'API Web Bluetooth consente ai siti web in esecuzione nel ruolo di server 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 a dispositivi nelle vicinanze utilizzando navigator.bluetooth.requestDevice, il browser chiede all'utente con un selettore di dispositivi dove può scegliere un dispositivo o annullare la richiesta.

Richiesta all'utente per il 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 dispositivi Bluetooth che pubblicizzano il servizio Battery GATT Bluetooth:

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

Tuttavia, 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 di 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 la chiave dei filtri name o persino a 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 dei servizi. In caso contrario, riceverai un errore 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 del 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 obbligatoria Bluetooth Company Identifier (Identificatore dell'azienda Bluetooth) denominata companyIdentifier. Puoi anche fornire un prefisso dati che filtri i dati del produttore dei dispositivi Bluetooth che iniziano con questo prefisso. Tieni presente che dovresti anche definire la chiave optionalServices per poter accedere a tutti i servizi non inclusi in un filtro dei servizi. In caso contrario, riceverai un messaggio di errore più tardi 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 dati per abbinare alcuni pattern nei dati del produttore. Per scoprire di più, consulta la spiegazione dei filtri dei dati Bluetooth.

Filtri di esclusione

L'opzione exclusionFilters in navigator.bluetooth.requestDevice() consente di escludere alcuni dispositivi dal selettore del browser. Può essere utilizzato per escludere 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. Dovrai anche definire la chiave optionalServices per poter accedere ad alcuni servizi. In caso contrario, riceverai un messaggio di errore quando cercherai 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

Cosa fai ora che hai un BluetoothDevice? Colleghiamoci 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); });

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

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


.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 di monitoraggio della frequenza cardiaca.

Ti garantisco che non c'è nessuna magia. Tutto è spiegato nella pagina Caratteristica del punto di controllo del 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 ricollegarsi:

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 device.gatt.disconnect() per scollegare l'app web dal dispositivo Bluetooth. Verranno attivati gli ascoltatori di eventi gattserverdisconnected esistenti. Tieni presente che NON interromperà la comunicazione del dispositivo Bluetooth se un'altra app sta già comunicando con il dispositivo Bluetooth. Per saperne di più, consulta l'esempio di disconnessione del dispositivo e l'esempio di ricollegamento automatico.

Leggere e scrivere nei descrittori Bluetooth

I descrittori GATT Bluetooth sono attributi che descrivono un valore caratteristico. 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 sanitario del dispositivo.

Nell'esempio seguente, health_thermometer è il servizio Termometro per la salute,measurement_interval la caratteristica Intervallo di misurazione egatt.characteristic_user_description il descrittore Characteristic User Description.

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

Samples, demo e codelab

Tutti i sample Bluetooth web riportati di seguito sono stati testati correttamente. Per sfruttare al meglio questi sample, ti consiglio di installare l'[app Android BLE Peripheral Simulator] che simula una periferica BLE con un servizio batteria, un servizio di misurazione della frequenza cardiaca o un servizio di termometro per la salute.

Principiante

  • Informazioni del dispositivo: recupera le informazioni di base di un dispositivo BLE.
  • Livello batteria: recupera le informazioni sulla batteria da un dispositivo BLE che pubblicizza le informazioni sulla batteria.
  • Ripristina energia: reimposta l'energia spesa da un dispositivo BLE che pubblicizza la frequenza cardiaca.
  • Proprietà delle caratteristiche: 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: disconnetti e ricevi una notifica dalla disconnessione di un dispositivo BLE dopo avervi eseguito la connessione.
  • Get Characteristics: recupera tutte le caratteristiche di un servizio pubblicizzato da un dispositivo BLE.
  • Ottieni descrittori: puoi ottenere 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. Consente di recuperare le informazioni di base del dispositivo da un dispositivo BLE dotato di filtri di esclusione di base.

Combinazione di più operazioni

Consulta anche le nostre demo di Web Bluetooth selezionate e i Codelab ufficiali di Web Bluetooth.

Biblioteche

  • web-bluetooth-utils è un modulo npm che aggiunge alcune funzioni pratiche all'API.
  • In noble, il modulo centrale BLE di Node.js più popolare, è disponibile uno shim dell'API Web Bluetooth. In questo modo puoi webpack/browserify noble senza bisogno di un server WebSocket o di altri plug-in.
  • angular-web-bluetooth è un modulo per Angular che esegue l'astrazione di tutto il codice boilerplate necessario per configurare l'API Web Bluetooth.

Strumenti

  • Iniziare a utilizzare il Bluetooth web è 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, 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 Bluetooth web per il tuo dispositivo Bluetooth.

Suggerimenti

In Chrome è disponibile la pagina Bluetooth Internals (Componenti interni Bluetooth) all'indirizzo about://bluetooth-internals, che ti consente di controllare 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 di Chrome per il debug dei dispositivi Bluetooth.

Ti consiglio inoltre di consultare la pagina ufficiale Come segnalare bug Bluetooth web, poiché 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 in fase di implementazione.

Anche se è ancora incompleta, ecco un'anteprima di cosa aspettarsi nel prossimo futuro:

  • La ricerca 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 vengono aggiunti o rimossi da un servizio GATT Bluetooth.

Mostra il supporto per l'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 all'account @ChromiumDev utilizzando l'hashtag #WebBluetooth e facci sapere dove e come lo utilizzi.

Risorse

Ringraziamenti

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