Un'introduzione a WebBluetooth

Pubblicato: 2022-03-10
Riepilogo rapido ↬ Con le app Web progressive, ora puoi utilizzare il Web per creare app complete. Grazie a un'enorme quantità di nuove specifiche e funzionalità, possiamo fare cose con il Web per cui prima avevi bisogno di scrivere app native. Tuttavia, parlare con dispositivi hardware era ancora un ponte troppo lontano fino ad ora. Grazie a WebBluetooth, ora possiamo costruire PWA in grado di controllare le tue luci, guidare un'auto o persino controllare un drone.

Con le Progressive Web Apps, il Web si sta avvicinando sempre di più alle app native. Tuttavia, con i vantaggi aggiuntivi inerenti al Web come la privacy e la compatibilità multipiattaforma.

Il Web è stato tradizionalmente fantastico nel parlare con i server sulla rete e in particolare con i server su Internet. Ora che il Web si sta spostando verso le applicazioni, abbiamo anche bisogno delle stesse funzionalità delle app native.

La quantità di nuove specifiche e funzionalità che sono state implementate negli ultimi anni nei browser è sbalorditiva. Abbiamo le specifiche per gestire il 3D come WebGL e l'imminente WebGPU. Possiamo trasmettere e generare audio, guardare video e utilizzare la webcam come dispositivo di input. Possiamo anche eseguire codice a velocità quasi native utilizzando WebAssembly. Inoltre, nonostante sia inizialmente un mezzo di sola rete, il web si è spostato verso il supporto offline con gli operatori dei servizi.

È fantastico e tutto, ma un'area è stata quasi il dominio esclusivo per le app native: la comunicazione con i dispositivi. Questo è un problema che stiamo cercando di risolvere da molto tempo, ed è qualcosa che probabilmente tutti hanno incontrato a un certo punto. Il web è ottimo per parlare con i server, ma non per parlare con i dispositivi . Pensa, ad esempio, a provare a configurare un router nella tua rete. È probabile che tu debba inserire un indirizzo IP e utilizzare un'interfaccia web su una semplice connessione HTTP senza alcuna sicurezza. Questa è solo una brutta esperienza e una cattiva sicurezza. Inoltre, come fai a sapere qual è l'indirizzo IP giusto?

Altro dopo il salto! Continua a leggere sotto ↓

HTTP è anche il primo problema che incontriamo quando proviamo a creare un'app Web progressiva che tenta di comunicare con un dispositivo. Le PWA sono solo HTTPS e i dispositivi locali sono sempre solo HTTP. Hai bisogno di un certificato per HTTPS e per ottenere un certificato, hai bisogno di un server pubblicamente disponibile con un nome di dominio (sto parlando di dispositivi sulla nostra rete locale che sono fuori portata).

Quindi per molti dispositivi sono necessarie app native per configurare i dispositivi e utilizzarli perché le app native non sono vincolate ai limiti della piattaforma web e possono offrire un'esperienza piacevole ai suoi utenti. Tuttavia, non voglio scaricare un'app da 500 MB per farlo. Forse il dispositivo che hai ha già qualche anno e l'app non è mai stata aggiornata per funzionare sul tuo nuovo telefono. Forse vuoi utilizzare un computer desktop o laptop e il produttore ha creato solo un'app mobile. Inoltre non è un'esperienza ideale.

WebBluetooth è una nuova specifica che è stata implementata in Chrome e Samsung Internet che ci consente di comunicare direttamente con i dispositivi Bluetooth Low Energy dal browser. Le Progressive Web App in combinazione con WebBluetooth offrono la sicurezza e la comodità di un'applicazione Web con il potere di parlare direttamente con i dispositivi.

Il Bluetooth ha un nome piuttosto brutto a causa della portata limitata, della cattiva qualità dell'audio e dei problemi di accoppiamento. Ma praticamente tutti questi problemi appartengono al passato. Bluetooth Low Energy è una specifica moderna che ha poco a che fare con le vecchie specifiche Bluetooth , a parte l'utilizzo dello stesso spettro di frequenza. Più di 10 milioni di dispositivi vengono forniti con il supporto Bluetooth ogni singolo giorno. Ciò include computer e telefoni, ma anche una varietà di dispositivi come monitor della frequenza cardiaca e della glicemia, dispositivi IoT come lampadine e giocattoli come auto e droni controllabili a distanza.

Letture consigliate : Capire le piattaforme basate su API: una guida per i product manager

La parte teorica noiosa

Poiché il Bluetooth stesso non è una tecnologia web, utilizza un vocabolario che potrebbe sembrarci poco familiare. Quindi esaminiamo come funziona il Bluetooth e alcuni dei termini.

Ogni dispositivo Bluetooth è un "dispositivo centrale" o una "periferica". Solo i dispositivi centrali possono avviare la comunicazione e possono parlare solo con le periferiche. Un esempio di dispositivo centrale potrebbe essere un computer o un telefono cellulare.

Una periferica non può avviare la comunicazione e può parlare solo con un dispositivo centrale. Inoltre, una periferica può parlare solo con un dispositivo centrale alla volta. Una periferica non può parlare con un'altra periferica.

un telefono nel mezzo, che parla con più periferiche, come un drone, un robot giocattolo, un cardiofrequenzimetro e una lampadina
Un dispositivo centrale può comunicare con più periferiche. (Grande anteprima)

Un dispositivo centrale può parlare con più periferiche contemporaneamente e può inoltrare messaggi se lo desidera. Quindi un cardiofrequenzimetro non potrebbe parlare con le tue lampadine, tuttavia, potresti scrivere un programma che gira su un dispositivo centrale che riceve la tua frequenza cardiaca e accende le luci rosse se la frequenza cardiaca supera una certa soglia.

Quando si parla di WebBluetooth si parla di una parte specifica della specifica Bluetooth chiamata Generic Attribute Profile, che ha l'ovvissima sigla GATT. (Apparentemente, GAP era già stato preso.)

Nell'ambito del GATT non si parla più di dispositivi centrali e periferiche, ma di client e server. Le tue lampadine sono server. Può sembrare controintuitivo, ma in realtà ha senso se ci pensi. La lampadina offre un servizio, cioè la luce. Proprio come quando il browser si connette a un server su Internet, il tuo telefono o computer è un client che si connette al server GATT nella lampadina.

Ogni server offre uno o più servizi. Alcuni di questi servizi fanno ufficialmente parte dello standard, ma puoi anche definire il tuo. Nel caso del cardiofrequenzimetro, esiste un servizio ufficiale definito nelle specifiche. Nel caso della lampadina, non c'è, e praticamente ogni produttore cerca di reinventare la ruota. Ogni servizio ha una o più caratteristiche. Ogni caratteristica ha un valore che può essere letto o scritto. Per ora, sarebbe meglio pensarlo come un array di oggetti, con ogni oggetto con proprietà che hanno valori.

la gerarchia dei servizi e delle caratteristiche rispetto a costrutti più familiari di JavaScript: un server è simile a un array di oggetti, un servizio a un oggetto in quell'array, una caratteristica a una proprietà di quell'oggetto ed entrambi hanno valori
Una gerarchia semplificata di servizi e caratteristiche. (Grande anteprima)

A differenza delle proprietà degli oggetti, i servizi e le caratteristiche non sono identificati da una stringa. Ogni servizio e caratteristica ha un UUID univoco che può essere lungo 16 o 128 bit. Ufficialmente, l'UUID a 16 bit è riservato agli standard ufficiali, ma praticamente nessuno segue questa regola. Infine, ogni valore è un array di byte. Non ci sono tipi di dati fantasiosi in Bluetooth.

Uno sguardo più da vicino a una lampadina Bluetooth

Diamo quindi un'occhiata a un vero dispositivo Bluetooth: una Mipow Playbulb Sphere. Puoi utilizzare un'app come BLE Scanner o nRF Connect per connetterti al dispositivo e vedere tutti i servizi e le caratteristiche. In questo caso, sto utilizzando l'app BLE Scanner per iOS.

La prima cosa che vedi quando ti colleghi alla lampadina è un elenco di servizi. Ce ne sono alcuni standardizzati come il servizio informazioni sul dispositivo e il servizio batteria. Ma ci sono anche alcuni servizi personalizzati. Sono particolarmente interessato al servizio con l'UUID a 16 bit di 0xff0f . Se apri questo servizio, puoi vedere un lungo elenco di caratteristiche. Non ho idea di cosa facciano la maggior parte di queste caratteristiche, poiché sono identificate solo da un UUID e perché purtroppo fanno parte di un servizio personalizzato; non sono standardizzati e il produttore non ha fornito alcuna documentazione.

La prima caratteristica con l'UUID di 0xfffc sembra particolarmente interessante. Ha un valore di quattro byte. Se cambiamo il valore di questi byte da 0x00000000 a 0x00ff0000 , la lampadina diventa rossa. Modificandolo in 0x0000ff00 , la lampadina diventa verde e 0x000000ff blu. Questi sono colori RGB e corrispondono esattamente ai colori esadecimali che utilizziamo in HTML e CSS.

Cosa fa quel primo byte? Bene, se cambiamo il valore in 0xff000000 , la lampadina diventa bianca. La lampadina contiene quattro diversi LED e, modificando il valore di ciascuno dei quattro byte, possiamo creare ogni singolo colore che vogliamo.

L'API WebBluetooth

È fantastico poter utilizzare un'app nativa per cambiare il colore di una lampadina, ma come possiamo farlo dal browser? Si scopre che con le conoscenze su Bluetooth e GATT che abbiamo appena appreso, questo è relativamente semplice grazie all'API WebBluetooth. Bastano solo un paio di righe di JavaScript per cambiare il colore di una lampadina.

Esaminiamo l'API WebBluetooth.

Connessione a un dispositivo

La prima cosa che dobbiamo fare è connetterci dal browser al dispositivo. Chiamiamo la funzione navigator.bluetooth.requestDevice() e forniamo alla funzione un oggetto di configurazione. Tale oggetto contiene informazioni su quale dispositivo vogliamo utilizzare e quali servizi dovrebbero essere disponibili per la nostra API.

Nell'esempio seguente, stiamo filtrando in base al nome del dispositivo, poiché vogliamo vedere solo i dispositivi che contengono il prefisso PLAYBULB nel nome. Stiamo anche specificando 0xff0f come servizio che vogliamo utilizzare. Poiché la funzione requestDevice() restituisce una promessa, possiamo attendere il risultato.

 let device = await navigator.bluetooth.requestDevice({ filters: [ { namePrefix: 'PLAYBULB' } ], optionalServices: [ 0xff0f ] });

Quando chiamiamo questa funzione, viene visualizzata una finestra con l'elenco dei dispositivi conformi ai filtri che abbiamo specificato. Ora dobbiamo selezionare manualmente il dispositivo a cui vogliamo connetterci. Questo è un passaggio essenziale per la sicurezza e la privacy e fornisce il controllo all'utente. L'utente decide se l'app Web può connettersi e, naturalmente, a quale dispositivo può connettersi. L'app Web non può ottenere un elenco di dispositivi o connettersi senza che l'utente selezioni manualmente un dispositivo.

il browser Chrome con la finestra che l'utente deve utilizzare per connettersi a un dispositivo, con la lampadina visibile nell'elenco dei dispositivi
L'utente deve connettersi manualmente selezionando un dispositivo. (Grande anteprima)

Dopo aver ottenuto l'accesso al dispositivo, possiamo connetterci al server GATT chiamando la funzione connect() sulla proprietà gatt del dispositivo e attendere il risultato.

 let server = await device.gatt.connect();

Una volta che abbiamo il server, possiamo chiamare getPrimaryService() sul server con l'UUID del servizio che vogliamo usare come parametro e attendere il risultato.

 let service = await server.getPrimaryService(0xff0f);

Quindi chiama getCharacteristic() sul servizio con l'UUID della caratteristica come parametro e attendi di nuovo il risultato.

Ora abbiamo le nostre caratteristiche che possiamo usare per scrivere e leggere i dati:

 let characteristic = await service.getCharacteristic(0xfffc);

Scrittura di dati

Per scrivere dati, possiamo chiamare la funzione writeValue() sulla caratteristica con il valore che vogliamo scrivere come ArrayBuffer, che è un metodo di archiviazione per dati binari. Il motivo per cui non possiamo utilizzare un array regolare è che gli array regolari possono contenere dati di vario tipo e possono anche avere buchi vuoti.

Dal momento che non possiamo creare o modificare direttamente un ArrayBuffer, stiamo usando invece un 'array tipizzato'. Ogni elemento di un array tipizzato è sempre dello stesso tipo e non presenta buchi. Nel nostro caso, useremo un Uint8Array , che non è firmato, quindi non può contenere numeri negativi; un intero, quindi non può contenere frazioni; ed è di 8 bit e può contenere solo valori da 0 a 255. In altre parole: un array di byte.

 characteristic.writeValue( new Uint8Array([ 0, r, g, b ]) );

Sappiamo già come funziona questa particolare lampadina. Dobbiamo fornire quattro byte, uno per ogni LED. Ogni byte ha un valore compreso tra 0 e 255, e in questo caso vogliamo utilizzare solo i LED rosso, verde e blu, quindi lasciamo spento il LED bianco, utilizzando il valore 0.

Lettura dei dati

Per leggere il colore corrente della lampadina, possiamo utilizzare la funzione readValue() e attendere il risultato.

 let value = await characteristic.readValue(); let r = value.getUint8(1); let g = value.getUint8(2); let b = value.getUint8(3);

Il valore che otteniamo è un DataView di un ArrayBuffer e offre un modo per estrarre i dati da ArrayBuffer. Nel nostro caso, possiamo usare la funzione getUint8() con un indice come parametro per estrarre i singoli byte dall'array.

Ricevere notifiche sui cambiamenti

Infine, c'è anche un modo per ricevere una notifica quando il valore di un dispositivo cambia. Non è molto utile per una lampadina, ma per il nostro cardiofrequenzimetro abbiamo valori in continua evoluzione e non vogliamo eseguire il polling manuale del valore corrente ogni singolo secondo.

 characteristic.addEventListener( 'characteristicvaluechanged', e => { let r = e.target.value.getUint8(1); let g = e.target.value.getUint8(2); let b = e.target.value.getUint8(3); } ); characteristic.startNotifications();

Per ottenere un callback ogni volta che un valore cambia, dobbiamo chiamare la funzione addEventListener() sulla caratteristica con il parametro characteristicvaluechanged e una funzione di callback. Ogni volta che il valore cambia, la funzione di callback verrà chiamata con un oggetto evento come parametro e possiamo ottenere i dati dalla proprietà value della destinazione dell'evento. Infine, estrai nuovamente i singoli byte dal DataView di ArrayBuffer.

Poiché la larghezza di banda sulla rete Bluetooth è limitata, dobbiamo avviare manualmente questo meccanismo di notifica chiamando startNotifications() sulla caratteristica. In caso contrario, la rete verrà inondata da dati non necessari. Inoltre, poiché questi dispositivi utilizzano tipicamente una batteria, ogni singolo byte che non dobbiamo inviare migliorerà definitivamente la durata della batteria del dispositivo perché la radio interna non ha bisogno di essere accesa così spesso.

Conclusione

Ora abbiamo superato il 90% dell'API WebBluetooth. Con poche chiamate di funzione e inviando 4 byte, puoi creare una web app che controlla i colori delle tue lampadine. Se aggiungi qualche riga in più, puoi persino controllare un'auto giocattolo o pilotare un drone. Con sempre più dispositivi Bluetooth che si fanno strada sul mercato, le possibilità sono infinite.

Ulteriori risorse

  • Bluetooth.rocks! demo | (Codice sorgente su GitHub)
  • "Specifica Web Bluetooth", gruppo della comunità Web Bluetooth
  • Open GATT Registry Una raccolta non ufficiale di documentazione per i servizi di attributo generico per dispositivi Bluetooth Low Energy.