O introducere în WebBluetooth

Publicat: 2022-03-10
Rezumat rapid ↬ Cu Progressive Web Apps, acum puteți utiliza web-ul pentru a crea aplicații complete. Datorită unui număr enorm de specificații și funcții noi, putem face lucruri cu web-ul pentru care aveai nevoie pentru a scrie aplicații native. Cu toate acestea, vorbirea cu dispozitivele hardware a fost încă o punte prea departe până acum. Datorită WebBluetooth, acum putem construi PWA-uri care vă pot controla luminile, pot conduce o mașină sau chiar controla o dronă.

Cu Progressive Web Apps, web-ul s-a îndreptat din ce în ce mai mult către aplicațiile native. Cu toate acestea, cu beneficiile suplimentare care sunt inerente web-ului, cum ar fi confidențialitatea și compatibilitatea între platforme.

Web-ul a fost în mod tradițional fantastic în ceea ce privește vorbirea cu serverele din rețea și în special cu serverele de pe Internet. Acum că web-ul se îndreaptă către aplicații, avem nevoie și de aceleași capacități pe care le au aplicațiile native.

Numărul de noi specificații și caracteristici care au fost implementate în ultimii ani în browsere este uluitor. Avem specificații pentru a trata 3D, cum ar fi WebGL și viitorul WebGPU. Putem transmite și genera audio, viziona videoclipuri și folosi camera web ca dispozitiv de intrare. De asemenea, putem rula cod la viteze aproape native folosind WebAssembly. Mai mult decât atât, în ciuda faptului că a fost inițial un mediu exclusiv de rețea, web-ul s-a mutat către suport offline cu lucrătorii de servicii.

Este grozav și totul, dar o zonă a fost aproape domeniul exclusiv pentru aplicațiile native: comunicarea cu dispozitivele. Aceasta este o problemă pe care încercăm să o rezolvăm de mult timp și este ceva cu care probabil s-a întâlnit toată lumea la un moment dat. Web-ul este excelent pentru a vorbi cu serverele, dar nu pentru a vorbi cu dispozitive . Gândiți-vă, de exemplu, la încercarea de a configura un router în rețeaua dvs. Sunt șanse să fi trebuit să introduceți o adresă IP și să utilizați o interfață web printr-o conexiune HTTP simplă, fără niciun fel de securitate. Aceasta este doar o experiență slabă și o securitate proastă. În plus, de unde știi care este adresa IP corectă?

Mai multe după săritură! Continuați să citiți mai jos ↓

HTTP este, de asemenea, prima problemă cu care ne confruntăm atunci când încercăm să creăm o aplicație web progresivă care încearcă să vorbească cu un dispozitiv. PWA sunt numai HTTPS, iar dispozitivele locale sunt întotdeauna doar HTTP. Aveți nevoie de un certificat pentru HTTPS, iar pentru a obține un certificat, aveți nevoie de un server disponibil public cu un nume de domeniu (vorbesc despre dispozitive din rețeaua noastră locală care nu sunt la îndemână).

Deci, pentru multe dispozitive, aveți nevoie de aplicații native pentru a configura dispozitivele și a le utiliza, deoarece aplicațiile native nu sunt legate de limitările platformei web și pot oferi o experiență plăcută utilizatorilor săi. Cu toate acestea, nu vreau să descarc o aplicație de 500 MB pentru a face asta. Poate că dispozitivul pe care îl aveți are deja câțiva ani, iar aplicația nu a fost niciodată actualizată pentru a rula pe noul dvs. telefon. Poate doriți să utilizați un computer desktop sau laptop, iar producătorul a creat doar o aplicație mobilă. De asemenea, nu este o experiență ideală.

WebBluetooth este o nouă specificație care a fost implementată în Chrome și Samsung Internet care ne permite să comunicăm direct cu dispozitivele Bluetooth Low Energy din browser. Progressive Web Apps în combinație cu WebBluetooth oferă securitatea și confortul unei aplicații web cu puterea de a vorbi direct cu dispozitivele.

Bluetooth are un nume destul de prost din cauza gamei limitate, a calității audio proaste și a problemelor de asociere. Dar, aproape toate aceste probleme aparțin trecutului. Bluetooth Low Energy este o specificație modernă care are puțin de-a face cu vechile specificații Bluetooth , în afară de utilizarea aceluiași spectru de frecvență. Peste 10 milioane de dispozitive sunt livrate cu suport Bluetooth în fiecare zi. Acestea includ computere și telefoane, dar și o varietate de dispozitive, cum ar fi monitoare pentru ritm cardiac și glucoză, dispozitive IoT precum becuri și jucării precum mașini controlabile de la distanță și drone.

Lectură recomandată : Înțelegerea platformelor bazate pe API: un ghid pentru managerii de produs

Partea teoretică plictisitoare

Deoarece Bluetooth în sine nu este o tehnologie web, folosește un vocabular care ne poate părea necunoscut. Deci, să trecem peste modul în care funcționează Bluetooth și o parte din terminologie.

Fiecare dispozitiv Bluetooth este fie un „dispozitiv central”, fie un „periferic”. Doar dispozitivele centrale pot iniția comunicarea și pot vorbi doar cu perifericele. Un exemplu de dispozitiv central ar fi un computer sau un telefon mobil.

Un periferic nu poate iniția comunicarea și poate vorbi doar cu un dispozitiv central. În plus, un periferic poate vorbi doar cu un dispozitiv central în același timp. Un periferic nu poate vorbi cu un alt periferic.

un telefon în mijloc, care vorbește cu mai multe periferice, cum ar fi o dronă, o jucărie robot, un monitor de ritm cardiac și un bec
Un dispozitiv central poate vorbi cu mai multe periferice. (Previzualizare mare)

Un dispozitiv central poate vorbi cu mai multe periferice în același timp și poate transmite mesaje dacă dorește. Deci, un monitor de ritm cardiac nu ar putea vorbi cu becurile tale, totuși, ai putea scrie un program care rulează pe un dispozitiv central care primește ritmul cardiac și aprinde luminile în roșu dacă ritmul cardiac depășește un anumit prag.

Când vorbim despre WebBluetooth, vorbim despre o parte specifică a specificației Bluetooth numită Generic Attribute Profile, care are abrevierea foarte evidentă GATT. (Aparent, GAP a fost deja luat.)

În contextul GATT, nu mai vorbim de dispozitive și periferice centrale, ci de clienți și servere. Becurile tale sunt servere. Poate părea contra-intuitiv, dar de fapt are sens dacă te gândești la asta. Becul ofera un serviciu, adica lumina. La fel ca atunci când browserul se conectează la un server de pe Internet, telefonul sau computerul tău este un client care se conectează la serverul GATT din bec.

Fiecare server oferă unul sau mai multe servicii. Unele dintre aceste servicii fac parte oficial din standard, dar le puteți defini și pe ale dvs. În cazul monitorului de ritm cardiac, există un serviciu oficial definit în caietul de sarcini. În cazul becului, nu există și aproape fiecare producător încearcă să reinventeze roata. Fiecare serviciu are una sau mai multe caracteristici. Fiecare caracteristică are o valoare care poate fi citită sau scrisă. Deocamdată, cel mai bine ar fi să ne gândim la el ca la o serie de obiecte, fiecare obiect având proprietăți care au valori.

ierarhia serviciilor și caracteristicilor în comparație cu constructele mai familiare din JavaScript - un server este similar cu o matrice de obiecte, un serviciu pentru un obiect din acea matrice, o caracteristică pentru o proprietate a acelui obiect și ambele au valori
O ierarhie simplificată a serviciilor și caracteristicilor. (Previzualizare mare)

Spre deosebire de proprietățile obiectelor, serviciile și caracteristicile nu sunt identificate printr-un șir. Fiecare serviciu și caracteristică are un UUID unic care poate avea o lungime de 16 sau 128 de biți. Oficial, UUID-ul pe 16 biți este rezervat standardelor oficiale, dar aproape nimeni nu respectă această regulă. În cele din urmă, fiecare valoare este o matrice de octeți. Nu există tipuri de date sofisticate în Bluetooth.

O privire mai atentă asupra unui bec Bluetooth

Deci, să ne uităm la un dispozitiv Bluetooth real: un Mipow Playbulb Sphere. Puteți utiliza o aplicație precum BLE Scanner sau nRF Connect pentru a vă conecta la dispozitiv și a vedea toate serviciile și caracteristicile. În acest caz, folosesc aplicația BLE Scanner pentru iOS.

Primul lucru pe care îl vedeți când vă conectați la bec este o listă de servicii. Există unele standardizate, cum ar fi serviciul de informații despre dispozitiv și serviciul de baterie. Dar există și câteva servicii personalizate. Sunt interesat în special de serviciul cu UUID-ul pe 16 biți al 0xff0f . Dacă deschideți acest serviciu, puteți vedea o listă lungă de caracteristici. Nu am idee ce fac majoritatea acestor caracteristici, deoarece sunt identificate doar printr-un UUID și pentru că, din păcate, fac parte dintr-un serviciu personalizat; nu sunt standardizate, iar producătorul nu a furnizat nicio documentație.

Prima caracteristică cu UUID- 0xfffc pare deosebit de interesantă. Are o valoare de patru octeți. Dacă schimbăm valoarea acestor octeți de la 0x00000000 la 0x00ff0000 , becul devine roșu. Schimbarea lui la 0x0000ff00 devine becul în verde și 0x000000ff în albastru. Acestea sunt culori RGB și corespund exact culorilor hexadecimale pe care le folosim în HTML și CSS.

Ce face primul octet? Ei bine, dacă schimbăm valoarea la 0xff000000 , becul devine alb. Becul conține patru LED-uri diferite și, modificând valoarea fiecăruia dintre cei patru octeți, putem crea fiecare culoare pe care o dorim.

API-ul WebBluetooth

Este fantastic că putem folosi o aplicație nativă pentru a schimba culoarea unui bec, dar cum facem acest lucru din browser? Se pare că, datorită cunoștințelor despre Bluetooth și GATT pe care tocmai le-am învățat, acest lucru este relativ simplu datorită API-ului WebBluetooth. Este nevoie doar de câteva rânduri de JavaScript pentru a schimba culoarea unui bec.

Să trecem peste API-ul WebBluetooth.

Conectarea La Un Dispozitiv

Primul lucru pe care trebuie să-l facem este să ne conectăm de la browser la dispozitiv. Apelăm funcția navigator.bluetooth.requestDevice() și oferim funcției un obiect de configurare. Acest obiect conține informații despre ce dispozitiv dorim să folosim și ce servicii ar trebui să fie disponibile pentru API-ul nostru.

În exemplul următor, filtrem pe numele dispozitivului, deoarece vrem să vedem doar dispozitivele care conțin prefixul PLAYBULB în nume. De asemenea, specificăm 0xff0f ca serviciu pe care dorim să-l folosim. Deoarece funcția requestDevice() returnează o promisiune, putem aștepta rezultatul.

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

Când apelăm această funcție, apare o fereastră cu lista de dispozitive care se conformează filtrelor pe care le-am specificat. Acum trebuie să selectăm manual dispozitivul la care dorim să ne conectăm. Acesta este un pas esențial pentru securitate și confidențialitate și oferă control utilizatorului. Utilizatorul decide dacă aplicația web are permisiunea de a se conecta și, bineînțeles, la ce dispozitiv este permis să se conecteze. Aplicația web nu poate obține o listă de dispozitive sau se poate conecta fără ca utilizatorul să selecteze manual un dispozitiv.

browserul Chrome cu fereastra pe care utilizatorul trebuie să o folosească pentru a se conecta la un dispozitiv, cu becul vizibil în lista de dispozitive
Utilizatorul trebuie să se conecteze manual selectând un dispozitiv. (Previzualizare mare)

După ce obținem acces la dispozitiv, ne putem conecta la serverul GATT apelând funcția connect() de pe proprietatea gatt a dispozitivului și așteptăm rezultatul.

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

Odată ce avem serverul, putem apela getPrimaryService() pe server cu UUID-ul serviciului pe care vrem să-l folosim ca parametru și așteptăm rezultatul.

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

Apoi apelați getCharacteristic() pe serviciu cu UUID-ul caracteristicii ca parametru și așteptați din nou rezultatul.

Acum avem caracteristicile noastre pe care le putem folosi pentru a scrie și a citi date:

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

Scrierea datelor

Pentru a scrie date, putem apela funcția writeValue() pe caracteristica cu valoarea pe care dorim să o scriem ca ArrayBuffer, care este o metodă de stocare a datelor binare. Motivul pentru care nu putem folosi o matrice obișnuită este că matricele obișnuite pot conține date de diferite tipuri și pot avea chiar găuri goale.

Deoarece nu putem crea sau modifica direct un ArrayBuffer, folosim în schimb o „matrice tipizată”. Fiecare element al unui tablou tipizat este întotdeauna de același tip și nu are găuri. În cazul nostru, vom folosi un Uint8Array , care este nesemnat, astfel încât nu poate conține niciun număr negativ; un număr întreg, deci nu poate conține fracții; și este de 8 biți și poate conține doar valori de la 0 la 255. Cu alte cuvinte: o matrice de octeți.

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

Știm deja cum funcționează acest bec special. Trebuie să oferim patru octeți, câte unul pentru fiecare LED. Fiecare octet are o valoare între 0 și 255, iar în acest caz, dorim să folosim doar LED-urile roșii, verzi și albastre, așa că lăsăm LED-ul alb stins, folosind valoarea 0.

Citirea datelor

Pentru a citi culoarea curentă a becului, putem folosi funcția readValue() și așteptăm rezultatul.

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

Valoarea pe care o primim este un DataView al unui ArrayBuffer și oferă o modalitate de a scoate datele din ArrayBuffer. În cazul nostru, putem folosi funcția getUint8() cu un index ca parametru pentru a scoate octeții individuali din matrice.

Primirea notificărilor despre modificări

În cele din urmă, există și o modalitate de a primi notificări atunci când valoarea unui dispozitiv se schimbă. Acest lucru nu este cu adevărat util pentru un bec, dar pentru monitorul nostru de ritm cardiac avem valori în continuă schimbare și nu vrem să interogăm manual valoarea actuală în fiecare secundă.

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

Pentru a obține un callback ori de câte ori o valoare se schimbă, trebuie să apelăm funcția addEventListener() pe caracteristică cu parametrul characteristicvaluechanged și o funcție de apel invers. Ori de câte ori valoarea se schimbă, funcția de apel invers va fi apelată cu un obiect eveniment ca parametru și putem obține datele din proprietatea value a țintei evenimentului. Și, în sfârșit, extrageți din nou octeții individuali din DataView al ArrayBuffer-ului.

Deoarece lățimea de bandă în rețeaua Bluetooth este limitată, trebuie să pornim manual acest mecanism de notificare apelând startNotifications() pe caracteristică. În caz contrar, rețeaua va fi inundată de date inutile. În plus, deoarece aceste dispozitive folosesc de obicei o baterie, fiecare octet pe care nu trebuie să-l trimitem va îmbunătăți definitiv durata de viață a bateriei dispozitivului, deoarece radioul intern nu trebuie pornit la fel de des.

Concluzie

Acum am depășit 90% din API-ul WebBluetooth. Cu doar câteva apeluri de funcție și trimițând 4 octeți, puteți crea o aplicație web care controlează culorile becurilor dvs. Dacă mai adaugi câteva rânduri, poți chiar să controlezi o mașină de jucărie sau să zbori cu o dronă. Cu tot mai multe dispozitive Bluetooth care își fac drum pe piață, posibilitățile sunt nesfârșite.

Resurse suplimentare

  • Bluetooth.rocks! Demo | (Codul sursă pe GitHub)
  • „Web Bluetooth Specification”, Web Bluetooth Community Group
  • Deschideți registrul GATT O colecție neoficială de documentație pentru serviciile de atribute generice pentru dispozitivele Bluetooth Low Energy.