Dispositivos USB

En este documento, se describe cómo usar la API de USB para comunicarse con dispositivos USB. No se puede acceder a algunos dispositivos a través de la API de USB (consulta la sección Advertencias a continuación para obtener más detalles). Las apps de Chrome también se pueden conectar a dispositivos serial y Bluetooth.

Para obtener información general sobre USB, consulta las especificaciones de USB oficiales. USB en un NutShell es un curso rápido razonable que puede resultarte útil.

Requisito del manifiesto

La API de USB requiere el permiso "usb" en el archivo de manifiesto:

"permissions": [
  "usb"
]

Además, para evitar las impresiones digitales, debes declarar todos los tipos de dispositivos a los que quieres acceder en el archivo de manifiesto. Cada tipo de dispositivo USB corresponde a un par de ID del proveedor/ID del producto (VID/PID). Puedes usar usb.getDevices para enumerar dispositivos por su par de VID/PID.

Debes declarar los pares de VID/PID de cada tipo de dispositivo que quieras usar con el permiso usbDevices en el archivo de manifiesto de tu app, como se muestra en el siguiente ejemplo:

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

A partir de Chrome 57, el requisito para declarar todos los tipos de dispositivos en el manifiesto de la app se relaja para las apps que se ejecutan como apps de kiosco de ChromeOS. En el caso de las aplicaciones de kiosco, puedes usar la propiedad de permiso interfaceClass para solicitar permiso de acceso a dispositivos USB que hagan lo siguiente:

  • Cómo implementar una interfaz USB de una clase de interfaz específica
  • tienen una clase de dispositivo USB específica

Por ejemplo, el siguiente permiso usbDevices otorgaría a una app acceso a todos los dispositivos USB que implementen una interfaz de impresora (código de clase de interfaz 7) y a dispositivos de concentrador USB (código de clase 9 del dispositivo):

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

Para obtener la lista de valores de interfaceClass aceptables, consulta Códigos de clase USB.

Se puede combinar la propiedad interfaceClass con la propiedad vendorId para obtener acceso únicamente a dispositivos USB de un proveedor específico, como se muestra en el siguiente ejemplo:

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

Buscando un dispositivo

Para determinar si uno o más dispositivos específicos están conectados al sistema de un usuario, usa el método usb.getDevices:

chrome.usb.getDevices(enumerateDevicesOptions, callback);
Parámetro (tipo)Descripción
EnumerateDevicesOptions (objeto)Es un objeto que especifica un vendorId (largo) y un productId (largo) que se usan para encontrar el tipo correcto de dispositivo en el autobús. Tu manifiesto debe declarar la sección de permisos usbDevices, en la que se enumeran todos los pares vendorId y deviceId a los que tu app desea acceder.
devolución de llamada (función)Se llama cuando finaliza la enumeración de dispositivos. La devolución de llamada se ejecutará con un parámetro: un array de objetos Device con tres propiedades: device, vendorId y productId. La propiedad del dispositivo es un identificador estable para un dispositivo conectado. No cambiará hasta que el dispositivo se desconecte. El detalle del identificador es opaco y está sujeto a cambios. No confíes en su tipo actual.
Si no se encuentran dispositivos, el array estará vacío.

Ejemplo:

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 de un dispositivo

Una vez que se muestran los objetos Device, puedes abrir un dispositivo usando usb.openDevice para obtener un controlador de conexión. Solo puedes comunicarte con dispositivos USB mediante controladores de conexión.

PropiedadDescripción
dispositivoSe recibió el objeto en la devolución de llamada usb.getDevices.
datos (arrayarray)Contiene los datos que envió el dispositivo si la transferencia era entrante.

Ejemplo:

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

Para simplificar el proceso de apertura, puedes usar el método usb.findDevices, que enumera, solicita acceso y abre los dispositivos en una llamada:

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

que equivale a lo siguiente:

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

Transferencias USB y recepción de datos de un dispositivo

El protocolo USB define cuatro tipos de transferencias: control, masiva, isocrónica e interrupción. Estas transferencias se describen a continuación.

Las transferencias pueden ocurrir en ambas direcciones: de dispositivo a host (entrante) y de host a dispositivo (saliente). Debido a la naturaleza del protocolo USB, el host (la computadora que ejecuta la app de Chrome) debe iniciar tanto los mensajes entrantes como los salientes. Para los mensajes entrantes (de un dispositivo a un host), el host (iniciado por el código JavaScript) envía un mensaje marcado como "entrante" al dispositivo. Los detalles del mensaje dependen del dispositivo, pero, por lo general, tendrán alguna identificación de lo que le solicites. Luego, el dispositivo responde con los datos solicitados. Chrome maneja la respuesta del dispositivo y la entrega de forma asíncrona a la devolución de llamada que especifiques en el método de transferencia. Un mensaje saliente (host a dispositivo) es similar, pero la respuesta no contiene datos que muestra el dispositivo.

Para cada mensaje del dispositivo, la devolución de llamada especificada recibirá un objeto de evento con las siguientes propiedades:

PropiedadDescripción
resultCode (número entero)0 significa éxito; otros valores indican un error. Se puede
leer una cadena de error desde chrome.extension.lastError cuando se indica
una falla.
datos (arrayarray)Contiene los datos que envió el dispositivo si la transferencia era entrante.

Ejemplo:

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

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

Transferencias de CONTROL

Las transferencias de control generalmente se usan para enviar o recibir parámetros de configuración o comando a un dispositivo USB. El método controlTransfer siempre envía al extremo 0/lecturas desde el extremo 0 y no se requiere ClaimInterface. El método es simple y recibe tres parámetros:

chrome.usb.controlTransfer(connectionHandle, transferInfo, transferCallback)
Parámetro (tipos)Descripción
connectionHandleEl objeto se recibió en la devolución de llamada usb.openDevice.
transferInfoObjeto de parámetro con valores de la tabla que aparece a continuación. Para obtener más información, consulta la especificación del protocolo del dispositivo USB.
transferCallback()Se invoca cuando se completa la transferencia.

Valores del objeto transferInfo:

ValorDescripción
requestType (string)“vendor”, “standard”, “class” o “reservado”.
destinatario (cadena)"device", "interface", "endpoint" o "other".
dirección (cadena)“in” o “out”. La dirección "en" se usa para notificar al dispositivo que
debe enviar información al host. Toda la comunicación en un bus
USB se inicia en el host, por lo que debes usar una transferencia "dentro" para permitir que un dispositivo
devuelva información.
solicitud (número entero)Definido por el protocolo de tu dispositivo.
valor (número entero)Definido por el protocolo de tu dispositivo.
índice (número entero)Definido por el protocolo de tu dispositivo.
longitud (número entero)Solo se usa cuando la dirección es "hacia dentro". Notifica al dispositivo que esta es la cantidad de datos que el host espera como respuesta.
datos (arrayarray)Definido por el protocolo de tu dispositivo, obligatorio cuando la dirección es "fuera".

Ejemplo:

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

Transferencias ISOCHRONOUS

Las transferencias isocrónicas son el tipo de transferencia USB más complejo. Se usan comúnmente para flujos de datos, como video y sonido. Para iniciar una transferencia isócrona (ya sea entrante o saliente), debes utilizar el método usb.isochronousTransfer:

chrome.usb.isochronousTransfer(connectionHandle, isochronousTransferInfo, transferCallback)
ParámetroDescripción
connectionHandleEl objeto se recibió en la devolución de llamada usb.openDevice.
isochronousTransferInfoEs un objeto de parámetro con los valores de la tabla de abajo.
transferCallback()Se invoca cuando se completa la transferencia.

Valores del objeto isochronousTransferInfo:

ValorDescripción
transferInfo (objeto)Un objeto con los siguientes atributos:
direction (string): "in" o "out".
extremo (número entero): definido por el dispositivo. Por lo general, se puede encontrar mediante una herramienta de instrospección USB, como lsusb -v
length (número entero): solo se usa cuando la dirección está "in". Le notifica al dispositivo que esta es la cantidad de datos que el host espera en respuesta.
Debe ser AL MENOS packets × packetLength.
datos (búfer de array): definidos por el protocolo de tu dispositivo; solo se usan cuando la dirección es "afuera".
paquetes (número entero)Cantidad total de paquetes esperados en esta transferencia.
packageLength (número entero)Longitud esperada de cada paquete en esta transferencia.

Ejemplo:

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

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

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

Transferencias BULK

Por lo general, las transferencias masivas se usan para transferir una gran cantidad de datos que no son urgentes de manera confiable. usb.bulkTransfer tiene tres parámetros:

chrome.usb.bulkTransfer(connectionHandle, transferInfo, transferCallback);
ParámetroDescripción
connectionHandleEl objeto se recibió en la devolución de llamada usb.openDevice.
transferInfoEs un objeto de parámetro con los valores de la tabla de abajo.
transferCallbackSe invoca cuando se completa la transferencia.

Valores del objeto transferInfo:

ValorDescripción
dirección (cadena)“in” o “out”.
extremo (número entero)Definido por el protocolo de tu dispositivo.
longitud (número entero)Solo se usa cuando la dirección es "hacia dentro". Notifica al dispositivo que esta es la cantidad de datos que el host espera como respuesta.
datos (ArrayBuffer)Definido por el protocolo de tu dispositivo; solo se usa cuando la dirección es "fuera".

Ejemplo:

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

Transferencias INTERRUPT

Las transferencias de interrupción se usan para datos sensibles de poca cantidad de tiempo. Como el host inicia todas las comunicaciones USB, el código de host suele sondear el dispositivo de forma periódica y envía transferencias de interrupción IN que harán que el dispositivo devuelva datos si hay algo en la cola de interrupción (mantenido por el dispositivo). usb.interruptTransfer tiene tres parámetros:

chrome.usb.interruptTransfer(connectionHandle, transferInfo, transferCallback);
ParámetroDescripción
connectionHandleEl objeto se recibió en la devolución de llamada usb.openDevice.
transferInfoEs un objeto de parámetro con los valores de la tabla de abajo.
transferCallbackSe invoca cuando se completa la transferencia. Ten en cuenta que esta devolución de llamada no contiene la respuesta del dispositivo. El propósito de la devolución de llamada es simplemente notificar a tu código que se procesaron las solicitudes de transferencia asíncronas.

Valores del objeto transferInfo:

ValorDescripción
dirección (cadena)“in” o “out”.
extremo (número entero)Definido por el protocolo de tu dispositivo.
longitud (número entero)Solo se usa cuando la dirección es "hacia dentro". Notifica al dispositivo que esta es la cantidad de datos que el host espera como respuesta.
datos (ArrayBuffer)Definido por el protocolo de tu dispositivo; solo se usa cuando la dirección es "fuera".

Ejemplo:

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

Advertencias

No se puede acceder a todos los dispositivos a través de la API de USB. En general, no es posible acceder a los dispositivos porque el kernel del sistema operativo o un controlador nativo los retienen del código del espacio del usuario. Algunos ejemplos son dispositivos con perfiles HID en sistemas OSX y memorias USB.

En la mayoría de los sistemas Linux, los dispositivos USB se asignan con permisos de solo lectura de forma predeterminada. Para abrir un dispositivo a través de esta API, el usuario también deberá tener acceso de escritura. Una solución simple es establecer una regla udev. Crea un archivo /etc/udev/rules.d/50-yourdevicename.rules con el siguiente contenido:

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

Luego, solo reinicia el daemon udev: service udev restart. Para verificar si los permisos del dispositivo están configurados correctamente, sigue estos pasos:

  • Ejecuta lsusb para encontrar los números de autobús y de dispositivo.
  • Ejecuta ls -al /dev/bus/usb/[bus]/[device]. Este archivo debe ser propiedad del grupo "plugdev" y tener permisos de escritura de grupo.

Tu app no puede hacerlo automáticamente debido a que este procedimiento requiere acceso con permisos de administrador. Te recomendamos que proporciones instrucciones para los usuarios finales y que incluyas un vínculo a la sección Advertencias de esta página para obtener una explicación.

En ChromeOS, simplemente llama a usb.requestAccess. El agente de permisos lo hace por ti.