USB 设备

本文档介绍了如何使用 USB API 与 USB 设备通信。某些设备无法通过 USB API 访问(如需了解详情,请参阅下文的注意事项部分)。Chrome 应用还可以连接到serial设备和蓝牙设备。

如需了解关于 USB 的背景信息,请参阅官方 USB 规范在 NutShell 中 USB 是合理的速成课,可能会对您有所帮助。

清单要求

USB API 需要清单文件中的“usb”权限:

"permissions": [
  "usb"
]

此外,为了防止“指纹”收集,您必须在清单文件中声明要访问的所有设备类型。每种类型的 USB 设备都对应一个供应商 ID/产品 ID (VID/PID) 对。您可以使用 usb.getDevices 按设备的 VID/PID 对枚举设备。

您必须在应用清单文件中的 usbDevices 权限下为要使用的每种设备类型声明 VID/PID 对,如以下示例所示:

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

Chrome 57 开始,对于作为 ChromeOS 自助服务终端应用运行的应用,可以放宽在应用清单中声明所有设备类型的要求。对于自助服务终端应用,您可以使用 interfaceClass 权限属性来请求访问符合以下条件的 USB 设备:

  • 实现特定接口类的 USB 接口
  • 具有特定的 USB 设备类

例如,以下 usbDevices 权限将授予应用访问所有实现打印机接口(接口类代码 7)的 USB 设备和 USB 集线器设备(设备类代码 9)的访问权限:

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

如需查看可接受的 interfaceClass 值的列表,请参阅 USB 类代码

您可将 interfaceClass 属性与 vendorId 属性结合使用,以便仅访问特定供应商的 USB 设备,如以下示例所示:

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

寻找设备

如需确定一个或多个特定设备是否已连接到用户的系统,请使用 usb.getDevices 方法:

chrome.usb.getDevices(enumerateDevicesOptions, callback);
参数(类型)说明
EnumerateDevicesOptions(对象)一个同时指定 vendorId(长整型)和 productId(长整型)的对象,用于在总线上查找正确的设备类型。您的清单必须声明 usbDevices 权限部分,其中列出应用要访问的所有 vendorIddeviceId 对。
callback(函数)在设备枚举完成时调用。回调将通过一个参数执行,即具有以下三个属性(devicevendorIdproductId)的 Device 对象数组。device 属性是已连接设备的稳定标识符。在设备拔下电源之前,该数字不会变化。标识符的详细信息不透明,可能会发生变化。请勿依赖其当前类型。
如果未找到任何设备,数组将为空。

例如:

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

打开设备

返回 Device 对象后,您可以使用 usb.openDevice 打开设备来获取连接句柄。您只能使用连接句柄与 USB 设备通信。

媒体资源说明
设备usb.getDevices 回调中收到的对象。
数据(数组缓冲区)包含设备发送的数据(如果传输是入站)。

例如:

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

为了简化打开过程,您可以使用 usb.findDevices 方法,该方法会在一次调用中枚举、请求访问权限和打开设备:

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

这相当于:

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

USB 传输和接收设备数据

USB 协议定义了四种类型的传输:控制批量等时中断。具体转移内容如下所述。

传输可以双向进行:设备到主机(入站)和主机到设备(出站)。由于 USB 协议的性质,入站和出站消息都必须由主机(运行 Chrome 应用的计算机)发起。对于入站(设备到主机)消息,主机(由 JavaScript 代码启动)向设备发送标记为“入站”的消息。消息的详细信息取决于设备,但通常可以确定您要向其请求的具体内容。然后,设备做出请求的数据作为响应。设备的响应由 Chrome 处理,并异步传送至您在传输方法中指定的回调。出站(主机到设备)消息与此类似,但响应不包含设备返回的数据。

对于来自设备的每条消息,指定的回调都将收到具有以下属性的事件对象:

媒体资源说明
resultCode(整数)0 表示成功;其他值表示失败。当指示失败时,
可以从 chrome.extension.lastError 读取错误字符串。

数据(数组缓冲区)包含设备发送的数据(如果传输是入站)。

例如:

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 传输

控制传输通常用于向 USB 设备发送或接收配置或命令参数。ControlTransfer 方法始终向端点 0 发送/从端点 0 发送数据,且不需要声明接口。该方法很简单,会接收三个参数:

chrome.usb.controlTransfer(connectionHandle, transferInfo, transferCallback)
参数(类型)说明
connectionHandleusb.openDevice 回调中收到的对象。
transferInfo包含下表中值的参数对象。有关详情,请查看您的 USB 设备协议规范。
transferCallback()转移完成时调用。

transferInfo 对象的值:

说明
requestType(字符串)“vendor”“standard”“class”或“reserve”。
收件人(字符串)“device”、“interface”、“endpoint”或“other”。
方向(字符串)“in”或“out”。“in”方向用于通知设备
它应该向主机发送信息。USB 总线上的所有通信
都由主机发起,因此请使用“输入”传输来让设备
发回信息。
请求(整数)由设备的协议定义。
value(整数)由设备的协议定义。
索引(整数)由设备的协议定义。
长度(整数)仅在方向为“in”时使用。通知设备这是主机响应预期的数据量。
数据(数组缓冲区)由设备的协议定义,当方向为“out”时为必填项。

例如:

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

ISOCHRONOUS 传输

等时传输是最复杂的 USB 传输类型。它们通常用于数据流,例如视频和声音。如需启动等时传输(入站或出站),您必须使用 usb.isochronousTransfer 方法:

chrome.usb.isochronousTransfer(connectionHandle, isochronousTransferInfo, transferCallback)
参数说明
connectionHandleusb.openDevice 回调中收到的对象。
isochronousTransferInfo参数对象及下表中的值。
transferCallback()转移完成时调用。

isochronousTransferInfo 对象的值:

说明
transferInfo(对象)一个具有以下属性的对象:
方向(字符串):“in”或“out”。
端点(整数):由您的设备定义。通常可通过 USB 数据分析工具找到,例如 lsusb -v
长度(整数) :仅在方向为“in”时使用。通知设备这是主机期望响应的数据量。
应至少为 packets × packetLength
数据(数组缓冲区) :由设备的协议定义;仅在方向为“输出”时使用。
数据包数量(整数)此传输中预期的数据包总数。
数据包长度(整数)此传输中每个数据包的预期长度。

例如:

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

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

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

BULK 传输

批量传输通常用于以可靠方式传输大量非时间敏感数据。usb.bulkTransfer 具有三个参数:

chrome.usb.bulkTransfer(connectionHandle, transferInfo, transferCallback);
参数说明
connectionHandleusb.openDevice 回调中收到的对象。
transferInfo参数对象及下表中的值。
transferCallback转移完成时调用。

transferInfo 对象的值:

说明
方向(字符串)“in”或“out”。
端点(整数)由设备的协议定义。
长度(整数)仅在方向为“in”时使用。通知设备这是主机响应预期的数据量。
数据 (ArrayBuffer)由设备的协议定义;仅在方向为“出”时使用。

例如:

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

INTERRUPT 传输

中断传输适用于少量敏感数据。由于所有 USB 通信均由主机发起,因此主机代码通常会定期轮询设备,同时发送中断 IN 传输,如果中断队列中有任何内容(由设备维护),会使设备发回数据。usb.interruptTransfer 具有三个参数:

chrome.usb.interruptTransfer(connectionHandle, transferInfo, transferCallback);
参数说明
connectionHandleusb.openDevice 回调中收到的对象。
transferInfo参数对象及下表中的值。
transferCallback转移完成时调用。请注意,此回调不包含设备的响应。回调的用途只是通知您的代码,异步传输请求已处理。

transferInfo 对象的值:

说明
方向(字符串)“in”或“out”。
端点(整数)由设备的协议定义。
长度(整数)仅在方向为“in”时使用。通知设备这是主机响应预期的数据量。
数据 (ArrayBuffer)由设备的协议定义;仅在方向为“出”时使用。

例如:

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

注意事项

并非所有设备都可以通过 USB API 访问。通常情况下,设备无法访问,因为操作系统的内核或原生驱动程序阻止了设备访问用户空间代码。例如,在 OSX 系统上具有 HID 配置文件的设备,以及 U 盘。

在大多数 Linux 系统上,默认情况下,USB 设备都具有只读权限。如需通过此 API 打开设备,用户还需拥有设备的写入权限。一种简单的解决方案是设置一条 udev 规则。创建一个包含以下内容的 /etc/udev/rules.d/50-yourdevicename.rules 文件:

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

然后,重启 udev 守护程序:service udev restart。您可以按照以下步骤检查设备权限是否设置正确:

  • 运行 lsusb 查找总线和设备编号。
  • 运行 ls -al /dev/bus/usb/[bus]/[device]。此文件应归“plugdev”组所有,并具有组写入权限。

您的应用无法自动执行此操作,因为此过程需要 root 访问权限。我们建议您向最终用户提供说明,并链接到本页面上的注意事项部分以查看相关说明。

在 ChromeOS 上,只需调用 usb.requestAccess 即可。权限代理会为您执行此操作。