L'API WebUSB rende USB più sicura e facile da usare portandola sul web.
Se dicessi semplicemente "USB", è molto probabile che tu penserebbe immediatamente a tastiere, mouse, audio, video e dispositivi di archiviazione. Hai ragione, ma esistono altri tipi di dispositivi Universal Serial Bus (USB).
Questi dispositivi USB non standardizzati richiedono ai fornitori di hardware di scrivere driver e SDK specifici per la piattaforma affinché tu (lo sviluppatore) possa utilizzarli. Purtroppo, questo codice specifico della piattaforma ha impedito storicamente l'utilizzo di questi dispositivi da parte del web. Ed è uno dei motivi per cui è stata creata l'API WebUSB: fornire un modo per esporre i servizi dei dispositivi USB al web. Con questa API, i produttori di hardware potranno creare SDK JavaScript multipiattaforma per i propri dispositivi.
Ma soprattutto questo rende il dispositivo USB più sicuro e più facile da usare portandolo sul web.
Vediamo il comportamento che ci si potrebbe aspettare con l'API WebUSB:
- Acquista un dispositivo USB.
- Collega il display al computer. Viene visualizzata immediatamente una notifica con il sito web corretto da visitare per questo dispositivo.
- Fai clic sulla notifica. Il sito web è pronto per essere utilizzato.
- Fai clic per connetterti e in Chrome viene visualizzato un selettore di dispositivi USB in cui puoi scegliere il tuo dispositivo.
Ecco fatto!
Come sarebbe questa procedura senza l'API WebUSB?
- Installa un'applicazione specifica per la piattaforma.
- Se è supportato anche sul mio sistema operativo, verifica che abbia scaricato la versione corretta.
- Installa l'elemento. Se sei fortunato, non riceverai richieste o popup del sistema operativo che ti avvisano di installare driver/app da internet. Se hai sfortuna, i driver o le applicazioni installati funzionano male e danneggiano il computer. Ricorda che il web è progettato per includere siti web con malfunzionamenti.
- Se utilizzi la funzionalità una sola volta, il codice rimane sul computer finché non lo rimuovi. Sul web, lo spazio per gli elementi inutilizzati viene eventualmente recuperato.
Prima di iniziare
Questo articolo presuppone che tu abbia alcune conoscenze di base sul funzionamento del connettore USB. In caso contrario, ti consiglio di leggere USB in a NutShell. Per informazioni di base sull'USB, consulta le specifiche USB ufficiali.
L'API WebUSB è disponibile in Chrome 61.
Disponibile per le prove dell'origine
Per ricevere il maggior numero possibile di feedback dagli sviluppatori che utilizzano l'API WebUSB sul campo, in precedenza abbiamo aggiunto questa funzionalità in Chrome 54 e Chrome 57 come prova dell'origine.
L'ultima prova è terminata correttamente a settembre 2017.
Privacy e sicurezza
Solo HTTPS
A causa della sua potenza, questa funzionalità funziona solo in contesti sicuri. Ciò significa che dovrai creare tenendo presente TLS.
Gesto dell'utente obbligatorio
Per motivi di sicurezza, navigator.usb.requestDevice()
può essere chiamato solo tramite un gesto dell'utente, ad esempio un tocco o un clic del mouse.
Norme relative alle autorizzazioni
Un criterio di autorizzazione è un meccanismo che consente agli sviluppatori di attivare e disattivare in modo selettivo varie API e funzionalità del browser. Può essere definito tramite un'intestazione HTTP e/o un attributo "allow" iframe.
Puoi definire un criterio di autorizzazione che controlla se l'attributo usb
è esposto nell'oggetto Navigator o, in altre parole, se consenti WebUSB.
Di seguito è riportato un esempio di criterio di intestazione in cui WebUSB non è consentito:
Feature-Policy: fullscreen "*"; usb "none"; payment "self" https://payment.example.com
Di seguito è riportato un altro esempio di criterio del contenitore in cui è consentita la porta USB:
<iframe allowpaymentrequest allow="usb; fullscreen"></iframe>
Iniziamo a programmare
L'API WebUSB si basa molto sulle promise di JavaScript. Se non le conosci, consulta questo fantastico tutorial sulle promesse. Un'altra cosa: () => {}
sono semplicemente funzioni Arrow di ECMAScript 2015.
Accedere ai dispositivi USB
Puoi chiedere all'utente di selezionare un singolo dispositivo USB collegato utilizzando
navigator.usb.requestDevice()
o chiamare navigator.usb.getDevices()
per ottenere un
elenco di tutti i dispositivi USB collegati a cui è stato concesso l'accesso al sito web.
La funzione navigator.usb.requestDevice()
accetta un oggetto JavaScript obbligatorio che definisce filters
. Questi filtri vengono utilizzati per associare qualsiasi dispositivo USB ai relativi identificativi del fornitore (vendorId
) e, facoltativamente, del prodotto (productId
).
Anche le chiavi classCode
, protocolCode
, serialNumber
e subclassCode
possono essere definite qui.
Ad esempio, ecco come accedere a un dispositivo Arduino connesso configurato per consentire l'origine.
navigator.usb.requestDevice({ filters: [{ vendorId: 0x2341 }] })
.then(device => {
console.log(device.productName); // "Arduino Micro"
console.log(device.manufacturerName); // "Arduino LLC"
})
.catch(error => { console.error(error); });
Prima che tu me lo chieda, non ho inventato questo numero esadecimale 0x2341
. Ho semplicemente cercato la parola "Arduino" in questo elenco di ID USB.
L'elemento USB device
restituito nella promessa soddisfatta di cui sopra contiene alcune informazioni di base ma importanti sul dispositivo, come la versione USB supportata, la dimensione massima del pacchetto, il fornitore e gli ID prodotto, nonché il numero di possibili configurazioni che il dispositivo può avere. Fondamentalmente, contiene tutti i campi del
descrittore USB del dispositivo.
// Get all connected USB devices the website has been granted access to.
navigator.usb.getDevices().then(devices => {
devices.forEach(device => {
console.log(device.productName); // "Arduino Micro"
console.log(device.manufacturerName); // "Arduino LLC"
});
})
A proposito, se un dispositivo USB annuncia il suo supporto per WebUSB e definisce un URL pagina di destinazione, Chrome mostrerà una notifica persistente quando il dispositivo USB viene collegato. Se fai clic su questa notifica, si aprirà la pagina di destinazione.
Parla a una scheda USB Arduino
Bene, ora vediamo quanto è facile comunicare da una scheda Arduino compatibile con WebUSB tramite la porta USB. Consulta le istruzioni all'indirizzo https://github.com/webusb/arduino per attivare WebUSB per i tuoi sketch.
Non preoccuparti, tratterò tutti i metodi del dispositivo WebUSB menzionati di seguito più avanti in questo articolo.
let device;
navigator.usb.requestDevice({ filters: [{ vendorId: 0x2341 }] })
.then(selectedDevice => {
device = selectedDevice;
return device.open(); // Begin a session.
})
.then(() => device.selectConfiguration(1)) // Select configuration #1 for the device.
.then(() => device.claimInterface(2)) // Request exclusive control over interface #2.
.then(() => device.controlTransferOut({
requestType: 'class',
recipient: 'interface',
request: 0x22,
value: 0x01,
index: 0x02})) // Ready to receive data
.then(() => device.transferIn(5, 64)) // Waiting for 64 bytes of data from endpoint #5.
.then(result => {
const decoder = new TextDecoder();
console.log('Received: ' + decoder.decode(result.data));
})
.catch(error => { console.error(error); });
Tieni presente che la libreria WebUSB che sto utilizzando implementa solo un protocollo di esempio (basato sul protocollo seriale USB standard) e che i produttori possono creare qualsiasi insieme e tipo di endpoint che preferiscono. I trasferimenti di controllo sono particolarmente utili per comandi di configurazione di piccole dimensioni, poiché hanno una priorità del bus e hanno una struttura ben definita.
Ed ecco lo sketch che è stato caricato sulla scheda Arduino.
// Third-party WebUSB Arduino library
#include <WebUSB.h>
WebUSB WebUSBSerial(1 /* https:// */, "webusb.github.io/arduino/demos");
#define Serial WebUSBSerial
void setup() {
Serial.begin(9600);
while (!Serial) {
; // Wait for serial port to connect.
}
Serial.write("WebUSB FTW!");
Serial.flush();
}
void loop() {
// Nothing here for now.
}
La libreria Arduino WebUSB di terze parti utilizzata nel codice di esempio riportato sopra fa principalmente due cose:
- Il dispositivo funge da dispositivo WebUSB che consente a Chrome di leggere l'URL pagina di destinazione.
- Espone un'API WebUSB Serial che puoi utilizzare per eseguire l'override di quella predefinita.
Controlla di nuovo il codice JavaScript. Una volta che l'utente ha scelto device
,
device
esegue tutti i passaggi specifici della piattaforma per avviare una sessione con il
dispositivo USB.device.open()
Quindi, devo selezionare una configurazione USB disponibile con
device.selectConfiguration()
. Ricorda che una configurazione specifica come viene alimentato il dispositivo, il suo consumo energetico massimo e il numero di interfacce.
A proposito di interfacce, devo anche richiedere l'accesso esclusivo con
device.claimInterface()
poiché i dati possono essere trasferiti a un'interfaccia o
gli endpoint associati solo quando l'interfaccia viene rivendicata. Infine, è necessario chiamare
device.controlTransferOut()
per configurare il dispositivo Arduino con i
comandi appropriati per comunicare tramite l'API seriale WebUSB.
Da qui, device.transferIn()
esegue un trasferimento collettivo sul
dispositivo per informarlo che l'host è pronto a ricevere dati collettivi. Poi, la promessa viene soddisfatta con un oggetto result
contenente una DataView data
che deve essere analizzata in modo appropriato.
Se hai familiarità con USB, tutto questo dovrebbe esserti familiare.
Voglio di più
L'API WebUSB ti consente di interagire con tutti i tipi di trasferimento/endpoint USB:
- I trasferimenti CONTROL, utilizzati per inviare o ricevere parametri di configurazione o comando a un dispositivo USB, vengono gestiti con
controlTransferIn(setup, length)
econtrolTransferOut(setup, data)
. - I trasferimenti INTERRUPT, utilizzati per una piccola quantità di dati sensibili al tempo, vengono gestiti con gli stessi metodi dei trasferimenti BULK con
transferIn(endpointNumber, length)
etransferOut(endpointNumber, data)
. - I trasferimenti ISOCRONICI, utilizzati per stream di dati come video e audio, vengono gestiti con
isochronousTransferIn(endpointNumber, packetLengths)
eisochronousTransferOut(endpointNumber, data, packetLengths)
. - I trasferimenti BULK, utilizzati per trasferire in modo affidabile una grande quantità di dati non urgenti, vengono gestiti con
transferIn(endpointNumber, length)
etransferOut(endpointNumber, data)
.
Potresti anche dare un'occhiata al progetto WebLight di Mike Tsao, che offre un esempio fondamentale di come costruire un dispositivo LED controllato tramite USB e progettato per l'API WebUSB (in questo caso non si utilizza Arduino). Troverai hardware, software e firmware.
Revocare l'accesso a un dispositivo USB
Il sito web può ripulire le autorizzazioni di accesso a un dispositivo USB di cui non ha più bisogno chiamando forget()
nell'istanza USBDevice
. Ad esempio, per un'applicazione web educativa utilizzata su un computer condiviso con molti dispositivi, un numero elevato di autorizzazioni generate dagli utenti accumulate crea un'esperienza utente negativa.
// Voluntarily revoke access to this USB device.
await device.forget();
Poiché forget()
è disponibile in Chrome 101 o versioni successive, controlla se questa funzionalità è supportata con quanto segue:
if ("usb" in navigator && "forget" in USBDevice.prototype) {
// forget() is supported.
}
Limiti di dimensione del trasferimento
Alcuni sistemi operativi impongono limiti alla quantità di dati che possono essere inclusi nelle transazioni USB in attesa. Suddividere i dati in transazioni più piccole e inviare solo alcune alla volta consente di evitare queste limitazioni. Inoltre, riduce la quantità di memoria utilizzata e consente all'applicazione di segnalare l'avanzamento man mano che i trasferimenti vengono completati.
Poiché più trasferimenti inviati a un endpoint vengono sempre eseguiti in ordine, è possibile migliorare il throughput inviando più chunk in coda per evitare la latenza tra i trasferimenti USB. Ogni volta che un chunk viene trasmesso completamente, il codice viene informato che deve fornire più dati, come descritto nell'esempio di funzione di supporto riportato di seguito.
const BULK_TRANSFER_SIZE = 16 * 1024; // 16KB
const MAX_NUMBER_TRANSFERS = 3;
async function sendRawPayload(device, endpointNumber, data) {
let i = 0;
let pendingTransfers = [];
let remainingBytes = data.byteLength;
while (remainingBytes > 0) {
const chunk = data.subarray(
i * BULK_TRANSFER_SIZE,
(i + 1) * BULK_TRANSFER_SIZE
);
// If we've reached max number of transfers, let's wait.
if (pendingTransfers.length == MAX_NUMBER_TRANSFERS) {
await pendingTransfers.shift();
}
// Submit transfers that will be executed in order.
pendingTransfers.push(device.transferOut(endpointNumber, chunk));
remainingBytes -= chunk.byteLength;
i++;
}
// And wait for last remaining transfers to complete.
await Promise.all(pendingTransfers);
}
Suggerimenti
Il debug USB in Chrome è più facile con la pagina interna about://device-log
dove puoi vedere tutti gli eventi relativi ai dispositivi USB in un unico posto.
Anche la pagina interna about://usb-internals
è utile e consente di simulare la connessione e la disconnessione dei dispositivi WebUSB virtuali.
Questa opzione è utile per eseguire test dell'interfaccia utente senza hardware reale.
Sulla maggior parte dei sistemi Linux, i dispositivi USB sono mappati con autorizzazioni di sola lettura per impostazione predefinita. Per consentire a Chrome di aprire un dispositivo USB, dovrai aggiungere una nuova regola udev. Crea un file in /etc/udev/rules.d/50-yourdevicename.rules
con i seguenti contenuti:
SUBSYSTEM=="usb", ATTR{idVendor}=="[yourdevicevendor]", MODE="0664", GROUP="plugdev"
dove [yourdevicevendor]
è 2341
se il tuo dispositivo è ad esempio un Arduino.
ATTR{idProduct}
può essere aggiunto anche per una regola più specifica. Assicurati che
user
sia un membro del gruppo plugdev
. Dopodiché, ricollega il dispositivo.
Risorse
- Stack Overflow: https://stackoverflow.com/questions/tagged/webusb
- Specifiche API WebUSB: http://wicg.github.io/webusb/
- Stato delle funzionalità di Chrome: https://www.chromestatus.com/feature/5651917954875392
- Problemi con le specifiche: https://github.com/WICG/webusb/issues
- Bug di implementazione: http://crbug.com?q=component:Blink>USB
- WebUSB ❤ ️Arduino: https://github.com/webusb/arduino
- IRC: #webusb su IRC di W3C
- Mailing list WICG: https://lists.w3.org/Archives/Public/public-wicg/
- Progetto WebLight: https://github.com/sowbug/weblight
Invia un tweet all'indirizzo @ChromiumDev utilizzando l'hashtag
#WebUSB
e facci sapere dove e come lo utilizzi.
Ringraziamenti
Grazie a Joe Medley per aver esaminato questo articolo.