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.

Fino ad ora, la possibilità di interagire con i dispositivi Bluetooth era possibile solo per le app specifiche della piattaforma. 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 su 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 si disconnette e persino leggere e scrivere nei descrittori Bluetooth. Per ulteriori informazioni, consulta la tabella Compatibilità del browser di MDN.

Per Linux e versioni precedenti di Windows, attiva 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, ti consiglio di leggere il post Web Bluetooth Security Model di Jeffrey Yasskin, un ingegnere del software del team di Chrome, che si occupa della 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 obbligatorio

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. Parliamo di ascoltare gli 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, consulta questo ottimo tutorial sulle promesse. Un'altra cosa:() => {} sono funzioni Arrow di ECMAScript 2015.

Richiedere dispositivi Bluetooth

Questa versione della specifica dell'API Web Bluetooth consente ai siti web in esecuzione nel ruolo Centro 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 utilizzandonavigator.bluetooth.requestDevice, il browser mostra all'utente un selettore di dispositivi in cui può scegliere un dispositivo o annullare la richiesta.

Richiesta all'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 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 pubblicizzato con la chiave di filtro name o anche un prefisso di questo nome con la chiave di filtro 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 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, verrà visualizzato un errore più tardi 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

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 seguente, 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 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 Notifiche mostra come interrompere le notifiche con stopNotifications() e rimuovere correttamente l'ascoltatore 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 la comunicazione con il dispositivo Bluetooth NON verrà interrotta se un'altra app è già in comunicazione 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 della caratteristica. Puoi leggerli e scriverli in modo simile alle caratteristiche GATT Bluetooth.

Vediamo ad esempio come leggere la descrizione dell'utente dell'intervallo di misurazione del termometro per il benessere 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.
  • Reimposta energia: reimposta l'energia spesa da un dispositivo BLE che pubblicizza il battito cardiaco.
  • 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.
  • Get Descriptors: recupera 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 corrispondono ai dati del produttore.
  • Filtri di esclusione: recupera le informazioni di base di un dispositivo BLE con 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 di utilità all'API.
  • In noble, il modulo centralizzato BLE di Node.js più popolare, è disponibile uno shim dell'API Web Bluetooth. In questo modo puoi webpack/browserify noble senza dover usare un server WebSocket o 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

  • 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, una caratteristica, definisci le relative proprietà e il gioco è fatto.
  • Se sei già uno sviluppatore Bluetooth, il plug-in di Studio per sviluppatori Web Bluetooth genererà anche il codice JavaScript Web Bluetooth 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 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 monitora i servizi GATT Bluetooth appena scoperti, mentre l'evento serviceremoved monitora 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 esaminato questo articolo. Immagine hero di SparkFun Electronics di Boulder, Stati Uniti.