本文档介绍了如何使用 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 权限部分,其中列出应用要访问的所有 vendorId 和 deviceId 对。 |
callback(函数) | 在设备枚举完成时调用。回调将通过一个参数执行,即具有以下三个属性(device 、vendorId 、productId )的 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)
参数(类型) | 说明 |
---|---|
connectionHandle | 在 usb.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)
参数 | 说明 |
---|---|
connectionHandle | 在 usb.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);
参数 | 说明 |
---|---|
connectionHandle | 在 usb.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);
参数 | 说明 |
---|---|
connectionHandle | 在 usb.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 即可。权限代理会为您执行此操作。