Dispositivi USB

Questo documento descrive come utilizzare l'API USB per comunicare con i dispositivi USB. Alcuni dispositivi non sono accessibili tramite l'API USB (per informazioni dettagliate, consulta la sezione Avvertenze di seguito). È possibile connettere le app di Chrome anche a dispositivi serial e Bluetooth.

Per informazioni di base sull'USB, consulta le specifiche USB ufficiali. USB in NutShell è un corso intensivo ragionevole che potresti trovare utile.

Requisito del file manifest

L'API USB richiede l'autorizzazione "usb" nel file manifest:

"permissions": [
  "usb"
]

Inoltre, per impedire l'impronta, devi dichiarare nel file manifest tutti i tipi di dispositivi a cui vuoi accedere. Ogni tipo di dispositivo USB corrisponde a una coppia di ID fornitore/ID prodotto (VID/PID). Puoi utilizzare usb.getDevices per enumerare i dispositivi in base alla coppia VID/PID.

Devi dichiarare le coppie VID/PID per ogni tipo di dispositivo che vuoi usare nell'ambito dell'autorizzazione usbDevices nel file manifest dell'app, come mostrato nell'esempio seguente:

"permissions": [
  {
    "usbDevices": [
      {
        "vendorId": 123,
        "productId": 456
      }
    ]
  }
]

A partire da Chrome 57, il requisito per la dichiarazione di tutti i tipi di dispositivi nel file manifest dell'app è semplificato per le app eseguite come app kiosk di ChromeOS. Per le app kiosk, puoi utilizzare la proprietà di autorizzazione interfaceClass per richiedere l'autorizzazione ad accedere ai dispositivi USB che:

  • implementare un'interfaccia USB di una specifica classe di interfaccia
  • hanno una classe di dispositivi USB specifica

Ad esempio, la seguente autorizzazione usbDevices concederebbe a un'app l'accesso a tutti i dispositivi USB che implementano un'interfaccia della stampante (codice classe interfaccia 7) e ai dispositivi hub USB (codice classe del dispositivo 9):

"permissions": [
  {
    "usbDevices": [
      {"interfaceClass": 7},
      {"interfaceClass": 9}
    ]
  }
]

Per l'elenco dei valori interfaceClass accettabili, consulta Codici dei corsi USB.

La proprietà interfaceClass può essere combinata con la proprietà vendorId per ottenere l'accesso solo ai dispositivi USB di un fornitore specifico, come dimostrato dall'esempio seguente:

"permissions": [
  {
    "usbDevices": [
      {
        "vendorId": 123,
        "interfaceClass": 7
      }
    ]
  }
]

Ricerca di un dispositivo

Per determinare se uno o più dispositivi specifici sono connessi al sistema di un utente, utilizza il metodo usb.getDevices:

chrome.usb.getDevices(enumerateDevicesOptions, callback);
Parametro (tipo)Descrizione
EnumerateDevicesOptions (oggetto)Un oggetto che specifica sia un vendorId (lungo) che un productId (lungo) utilizzato per trovare il tipo corretto di dispositivo sul bus. Il file manifest deve dichiarare la sezione delle autorizzazioni usbDevices che elenca tutte le coppie vendorId e deviceId a cui la tua app vuole accedere.
callback (funzione)Richiamato al termine dell'enumerazione del dispositivo. Il callback verrà eseguito con un parametro, un array di oggetti Device con tre proprietà: device, vendorId, productId. La proprietà del dispositivo è un identificatore stabile di un dispositivo connesso. Non cambierà finché il dispositivo non verrà scollegato. I dettagli dell'identificatore sono opachi e soggetti a modifiche. Non fare affidamento sul tipo corrente.
Se non vengono trovati dispositivi, l'array sarà vuoto.

Esempio:

function onDeviceFound(devices) {
  this.devices=devices;
  if (devices) {
    if (devices.length > 0) {
      console.log("Device(s) found: "+devices.length);
    } else {
      console.log("Device could not be found");
    }
  } else {
    console.log("Permission denied.");
  }
}

chrome.usb.getDevices({"vendorId": vendorId, "productId": productId}, onDeviceFound);

Apertura di un dispositivo

Una volta restituiti gli oggetti Device, puoi aprire un dispositivo utilizzando usb.openDevice per ottenere un handle di connessione. Puoi comunicare con i dispositivi USB solo utilizzando gli handle di connessione.

ProprietàDescrizione
dispositivoOggetto ricevuto nel callback usb.getDevices.
dati (buffer array)Contiene i dati inviati dal dispositivo se il trasferimento era in entrata.

Esempio:

var usbConnection = null;
var onOpenCallback = function(connection) {
  if (connection) {
    usbConnection = connection;
    console.log("Device opened.");
  } else {
    console.log("Device failed to open.");
  }
};

chrome.usb.openDevice(device, onOpenCallback);

Per semplificare la procedura di apertura, puoi utilizzare il metodo usb.findDevices, che enumera, richiede l'accesso e apre i dispositivi con una sola chiamata:

chrome.usb.findDevices({"vendorId": vendorId, "productId": productId, "interfaceId": interfaceId}, callback);

che equivale a:

chrome.usb.getDevices({"vendorId": vendorId, "productId": productId}, function (devices) {
  if (!devices) {
    console.log("Error enumerating devices.");
    callback();
    return;
  }
  var connections = [], pendingAccessRequests = devices.length;
  devices.forEach(function (device) {
    chrome.usb.requestAccess(interfaceId, function () {
      // No need to check for errors at this point.
      // Nothing can be done if an error occurs anyway. You should always try
      // to open the device.
      chrome.usb.openDevices(device, function (connection) {
        if (connection) connections.push(connection);
        pendingAccessRequests--;
        if (pendingAccessRequests == 0) {
          callback(connections);
        }
      });
    });
  })
});

Trasferimenti USB e ricezione di dati da un dispositivo

Il protocollo USB definisce quattro tipi di trasferimenti: control, bulk, isocrono e interruzione. Questi trasferimenti sono descritti di seguito.

I trasferimenti possono avvenire in entrambe le direzioni: da dispositivo a host (in entrata) e da host a dispositivo (in uscita). A causa della natura del protocollo USB, i messaggi in entrata e in uscita devono essere avviati dall'host (il computer su cui viene eseguita l'app Chrome). Per i messaggi in entrata (da dispositivo a host), l'host (avviato dal codice JavaScript) invia un messaggio contrassegnato come "in entrata" al dispositivo. I dettagli del messaggio dipendono dal dispositivo, ma di solito presentano una certa identificazione di ciò che viene richiesto. Il dispositivo risponde con i dati richiesti. La risposta del dispositivo viene gestita da Chrome e pubblicata in modo asincrono al callback specificato nel metodo di trasferimento. Un messaggio in uscita (host-to-device) è simile, ma la risposta non contiene dati restituiti dal dispositivo.

Per ogni messaggio del dispositivo, il callback specificato riceverà un oggetto evento con le seguenti proprietà:

ProprietàDescrizione
resultCode (numero intero)0 indica che l'operazione è riuscita, mentre altri valori indicano un errore. È possibile
leggere una stringa di errore da chrome.extension.lastError quando viene indicato un
errore.
dati (buffer array)Contiene i dati inviati dal dispositivo se il trasferimento era in entrata.

Esempio:

var onTransferCallback = function(event) {
   if (event && event.resultCode === 0 && event.data) {
     console.log("got " + event.data.byteLength + " bytes");
   }
};

chrome.usb.bulkTransfer(connectionHandle, transferInfo, onTransferCallback);

CONTROL trasferimenti

I trasferimenti di controllo vengono generalmente utilizzati per inviare o ricevere parametri di configurazione o comando a un dispositivo USB. Il metodo controlTransfer invia sempre/legge dall'endpoint 0 e non è richiesta alcuna claimInterface. Il metodo è semplice e riceve tre parametri:

chrome.usb.controlTransfer(connectionHandle, transferInfo, transferCallback)
Parametri (tipi)Descrizione
connectionHandleOggetto ricevuto nel callback usb.openDevice.
transferInfoOggetto parametro con i valori della tabella seguente. Controlla le specifiche del protocollo del dispositivo USB per i dettagli.
transferCallback()Richiamato al termine del trasferimento.

Valori per l'oggetto transferInfo:

ValoreDescrizione
requestType (stringa)"fornitore", "standard", "classe" o "prenotato".
destinatario (stringa)"dispositivo", "interfaccia", "endpoint" o "altro".
direzione (stringa)"in" o "out". La direzione di entrata viene utilizzata per notificare al dispositivo che
dovrebbe inviare informazioni all'host. Tutte le comunicazioni su un bus
USB vengono avviate dall'host, quindi utilizza un trasferimento "in entrata" per consentire a un dispositivo di
restituire le informazioni.
richiesta (numero intero)Definito in base al protocollo del dispositivo.
valore (intero)Definito in base al protocollo del dispositivo.
indice (numero intero)Definito in base al protocollo del dispositivo.
length (numero intero)Utilizzato solo quando la direzione è "in". Comunica al dispositivo che si tratta della quantità di dati che l'host si aspetta in risposta.
dati (buffer array)Definita dal protocollo del dispositivo, necessaria quando la direzione è "Fuori".

Esempio:

var transferInfo = {
  "requestType": "vendor",
   "recipient": "device",
  "direction": "out",
  "request":  0x31,
  "value": 120,
  "index": 0,
  // Note that the ArrayBuffer, not the TypedArray itself is used.
  "data": new Uint8Array([4, 8, 15, 16, 23, 42]).buffer
};
chrome.usb.controlTransfer(connectionHandle, transferInfo, optionalCallback);

Trasferimenti ISOCHRONOUS

I trasferimenti sincroni sono il tipo più complesso di trasferimenti USB. Sono comunemente utilizzati per i flussi di dati, come video e audio. Per avviare un trasferimento sincronizzato (in entrata o in uscita), devi utilizzare il metodo usb.isochronousTransfer:

chrome.usb.isochronousTransfer(connectionHandle, isochronousTransferInfo, transferCallback)
ParametroDescrizione
connectionHandleOggetto ricevuto nel callback usb.openDevice.
isochronousTransferInfoOggetto parametro con i valori nella tabella seguente.
transferCallback()Richiamato al termine del trasferimento.

Valori per l'oggetto isochronousTransferInfo:

ValoreDescrizione
TransferInfo (oggetto)Un oggetto con i seguenti attributi:
direzione (stringa): "in" o "out".
endpoint (numero intero): definito dal tuo dispositivo. In genere si trovano nello strumento di ispezione USB, come lsusb -v
length (integer): utilizzato solo quando la direzione è "in". Comunica al dispositivo che questa è la quantità di dati che l'host si aspetta in risposta.
Almeno packets × packetLength.
dati (arraybuffer): definiti dal protocollo del dispositivo; utilizzata solo quando la direzione è "out".
pacchetti (numero intero)Numero totale di pacchetti previsti in questo trasferimento.
PackageLength (numero intero)Lunghezza prevista di ogni pacchetto in questo trasferimento.

Esempio:

var transferInfo = {
  "direction": "in",
  "endpoint": 1,
  "length": 2560
};

var isoTransferInfo = {
  "transferInfo": transferInfo,
  "packets": 20,
  "packetLength": 128
};

chrome.usb.isochronousTransfer(connectionHandle, isoTransferInfo, optionalCallback);

Trasferimenti in blocco

I trasferimenti collettivi vengono comunemente utilizzati per trasferire in modo affidabile una grande quantità di dati non sensibili al fattore tempo. usb.bulkTransfer ha tre parametri:

chrome.usb.bulkTransfer(connectionHandle, transferInfo, transferCallback);
ParametroDescrizione
connectionHandleOggetto ricevuto nel callback usb.openDevice.
transferInfoOggetto parametro con i valori nella tabella seguente.
transferCallbackRichiamato al termine del trasferimento.

Valori per l'oggetto transferInfo:

ValoreDescrizione
direzione (stringa)"in" o "out".
endpoint (numero intero)Definito in base al protocollo del dispositivo.
length (numero intero)Utilizzato solo quando la direzione è "in". Comunica al dispositivo che si tratta della quantità di dati che l'host si aspetta in risposta.
dati (ArrayBuffer)Definita dal protocollo del dispositivo; utilizzata solo quando la direzione è "Fuori".

Esempio:

var transferInfo = {
  "direction": "out",
  "endpoint": 1,
  "data": new Uint8Array([4, 8, 15, 16, 23, 42]).buffer
};

INTERRUPT trasferimenti

I trasferimenti interrotti vengono utilizzati per quantità ridotte di dati sensibili al tempo. Poiché tutta la comunicazione USB è avviata dall'host, il codice host di solito esegue il polling del dispositivo periodicamente, inviando trasferimenti di interruzioni IN che fanno sì che il dispositivo restituisca i dati se è presente qualcosa nella coda di interruzioni (gestita dal dispositivo). usb.interruptTransfer ha tre parametri:

chrome.usb.interruptTransfer(connectionHandle, transferInfo, transferCallback);
ParametroDescrizione
connectionHandleOggetto ricevuto nel callback usb.openDevice.
transferInfoOggetto parametro con i valori nella tabella seguente.
transferCallbackRichiamato al termine del trasferimento. Tieni presente che questo callback non contiene la risposta del dispositivo. Lo scopo del callback è semplicemente notificare al codice che le richieste di trasferimento asincrone sono state elaborate.

Valori per l'oggetto transferInfo:

ValoreDescrizione
direzione (stringa)"in" o "out".
endpoint (numero intero)Definito in base al protocollo del dispositivo.
length (numero intero)Utilizzato solo quando la direzione è "in". Comunica al dispositivo che si tratta della quantità di dati che l'host si aspetta in risposta.
dati (ArrayBuffer)Definita dal protocollo del dispositivo; utilizzata solo quando la direzione è "Fuori".

Esempio:

var transferInfo = {
  "direction": "in",
  "endpoint": 1,
  "length": 2
};
chrome.usb.interruptTransfer(connectionHandle, transferInfo, optionalCallback);

Avvertenze

Non tutti i dispositivi sono accessibili tramite l'API USB. In generale, i dispositivi non sono accessibili perché il kernel del sistema operativo o un driver nativo li trattiene dal codice dello spazio utente. Alcuni esempi sono i dispositivi con profili HID sui sistemi OSX e le penne USB.

Sulla maggior parte dei sistemi Linux, i dispositivi USB sono mappati con autorizzazioni di sola lettura per impostazione predefinita. Per aprire un dispositivo tramite questa API, l'utente dovrà avere anche accesso in scrittura al dispositivo. Una soluzione semplice è impostare una regola udev. Crea un file /etc/udev/rules.d/50-yourdevicename.rules con i seguenti contenuti:

SUBSYSTEM=="usb", ATTR{idVendor}=="[yourdevicevendor]", MODE="0664", GROUP="plugdev"

Quindi, riavvia il daemon udev: service udev restart. Puoi controllare se le autorizzazioni del dispositivo sono impostate correttamente seguendo questa procedura:

  • Esegui lsusb per trovare i numeri di autobus e dispositivi.
  • Esegui ls -al /dev/bus/usb/[bus]/[device]. Questo file dovrebbe essere di proprietà del gruppo "plugdev" e disporre delle autorizzazioni di scrittura del gruppo.

L'app non può eseguire questa operazione automaticamente poiché questa procedura richiede l'accesso root. Ti consigliamo di fornire istruzioni agli utenti finali e di includere un link alla sezione Avvertenze di questa pagina per una spiegazione.

Su ChromeOS, basta chiamare usb.requestAccess. L'agente di autorizzazione lo fa per te.