Thiết bị USB

Tài liệu này mô tả cách sử dụng API USB để giao tiếp với các thiết bị USB. Một số thiết bị không thể truy cập qua API USB (xem phần Lưu ý dưới đây để biết chi tiết). Ứng dụng Chrome cũng có thể kết nối với thiết bị nối tiếpBluetooth.

Để biết thông tin cơ bản về USB, hãy xem Thông số kỹ thuật chính thức của USB. USB trong NutShell là một khoá học nhanh hợp lý mà bạn có thể thấy hữu ích.

Yêu cầu về tệp kê khai

API USB yêu cầu "usb" quyền trong tệp kê khai:

"permissions": [
  "usb"
]

Ngoài ra, để ngăn in vân tay, bạn phải khai báo tất cả các loại thiết bị mà mình muốn truy cập trong tệp kê khai. Mỗi loại thiết bị USB tương ứng với một mã nhà cung cấp/mã sản phẩm cặp (VID/PID). Bạn có thể sử dụng usb.getDevices để liệt kê các thiết bị theo cặp VID/PID của chúng.

Bạn phải khai báo cặp VID/PID cho từng loại thiết bị bạn muốn sử dụng trong usbDevices quyền trong tệp kê khai của ứng dụng, như trong ví dụ dưới đây:

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

Kể từ Chrome 57, bạn phải đáp ứng yêu cầu khai báo tất cả loại thiết bị trong tệp kê khai ứng dụng thoải mái cho những ứng dụng chạy dưới dạng ứng dụng kiosk của ChromeOS. Đối với ứng dụng kiosk, bạn có thể sử dụng Thuộc tính quyền của interfaceClass để yêu cầu cấp quyền truy cập vào các thiết bị USB:

  • triển khai giao diện USB của một lớp giao diện cụ thể
  • có một lớp thiết bị USB cụ thể

Ví dụ: Quyền usbDevices sau đây sẽ cấp cho một ứng dụng quyền truy cập vào mọi thiết bị USB triển khai giao diện máy in (mã lớp giao diện 7) và tới các thiết bị bộ chia USB (mã lớp thiết bị) 9):

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

Để biết danh sách các giá trị interfaceClass được chấp nhận, hãy xem phần Mã lớp USB.

Bạn có thể kết hợp thuộc tính interfaceClass với thuộc tính vendorId để chỉ truy cập được vào USB thiết bị của một nhà cung cấp cụ thể, như được minh hoạ trong ví dụ sau:

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

Tìm thiết bị

Để xác định xem một hoặc nhiều thiết bị cụ thể có được kết nối với hệ thống của người dùng hay không, hãy sử dụng usb.getDevices:

chrome.usb.getDevices(enumerateDevicesOptions, callback);
Thông số (loại)Mô tả
EnumerateDevicesOptions (đối tượng)Một đối tượng chỉ định cả vendorId (dài) và productId (dài) dùng để tìm đúng loại thiết bị trên xe buýt. Tệp kê khai phải khai báo phần quyền usbDevices liệt kê mọi cặp vendorIddeviceId mà ứng dụng của bạn muốn truy cập.
lệnh gọi lại (hàm)Được gọi khi quá trình liệt kê thiết bị hoàn tất. Lệnh gọi lại sẽ được thực thi bằng một tham số là một mảng gồm các đối tượng Device có 3 thuộc tính: device, vendorId, productId. Thuộc tính thiết bị là giá trị nhận dạng ổn định cho một thiết bị đã kết nối. Chế độ này sẽ không thay đổi cho đến khi bạn rút thiết bị ra khỏi nguồn điện. Thông tin chi tiết của giá trị nhận dạng này chưa rõ ràng và có thể thay đổi. Đừng dựa vào loại hiện tại của mảng.
Nếu không tìm thấy thiết bị nào, mảng sẽ trống.

Ví dụ:

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

Mở thiết bị

Sau khi đối tượng Device được trả về, bạn có thể mở một thiết bị bằng usb.openDevice để lấy điểm điều khiển kết nối. Bạn chỉ có thể giao tiếp với thiết bị USB bằng tay cầm kết nối.

Thuộc tínhMô tả
thiết bịĐã nhận được đối tượng trong lệnh gọi lại usb.getDevices.
dữ liệu (vùng đệm mảng)Chứa dữ liệu do thiết bị gửi nếu dữ liệu chuyển là dữ liệu đến.

Ví dụ:

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

Để đơn giản hoá quá trình mở, bạn có thể sử dụng phương thức usb.findDevices, trong đó liệt kê yêu cầu quyền truy cập và mở thiết bị trong một cuộc gọi:

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

tương đương với:

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

Truyền và nhận dữ liệu qua USB từ một thiết bị

Giao thức USB xác định 4 phương thức truyền: kiểm soát, hàng loạt, đồng bộgián đoạn. Những quá trình chuyển này được mô tả bên dưới.

Hoạt động chuyển dữ liệu có thể diễn ra theo cả hai hướng: từ thiết bị đến máy chủ (đến) và từ máy chủ đến thiết bị (ra ngoài). Hạn bản chất của giao thức USB, cả thư đến và thư đi đều phải do máy chủ khởi tạo (máy tính chạy ứng dụng Chrome). Đối với thư đến (từ thiết bị đến máy chủ), máy chủ (được thực hiện) bằng mã JavaScript của bạn) sẽ gửi thông báo được gắn cờ là "gửi đến" đối với thiết bị. Thông tin chi tiết về tin nhắn phụ thuộc vào thiết bị, nhưng thường sẽ có một số thông tin nhận dạng về nội dung bạn đang yêu cầu từ đó. Sau đó, thiết bị sẽ phản hồi bằng dữ liệu được yêu cầu. Phản hồi của thiết bị được xử lý bởi Chrome và phân phối không đồng bộ cho lệnh gọi lại mà bạn chỉ định trong phương thức chuyển. Sự kiện ra ngoài Thông báo (từ máy chủ đến thiết bị) cũng tương tự, nhưng phản hồi không chứa dữ liệu được thiết bị trả về.

Đối với mỗi thông báo từ thiết bị, lệnh gọi lại được chỉ định sẽ nhận một đối tượng sự kiện có thuộc tính các thuộc tính sau:

Thuộc tínhMô tả
kết quả (số nguyên)0 là thành công; các giá trị khác cho biết không thành công. Chuỗi lỗi có thể được
đọc từ chrome.extension.lastError khi hệ thống
chỉ báo lỗi.
dữ liệu (vùng đệm mảng)Chứa dữ liệu do thiết bị gửi nếu dữ liệu chuyển là dữ liệu đến.

Ví dụ:

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 chuyển tuyến

Truyền quyền kiểm soát thường dùng để gửi hoặc nhận các tham số cấu hình hoặc lệnh tới USB thiết bị. Phương thức controlTransfer luôn gửi đến/đọc từ điểm cuối 0 và không có claimInterface là bắt buộc. Phương thức này rất đơn giản và nhận được 3 tham số:

chrome.usb.controlTransfer(connectionHandle, transferInfo, transferCallback)
Thông số (loại)Mô tả
connectionHandleĐã nhận được đối tượng trong lệnh gọi lại usb.openDevice.
transferInfoĐối tượng thông số với các giá trị từ bảng bên dưới. Hãy kiểm tra thông số kỹ thuật về giao thức của thiết bị USB để biết thông tin chi tiết.
transferCallback()Được gọi khi quá trình chuyển hoàn tất.

Giá trị cho đối tượng transferInfo:

Giá trịMô tả
requestType (chuỗi)"nhà cung cấp", "tiêu chuẩn", "loại" hoặc "dành riêng".
người nhận (chuỗi)"thiết bị", "giao diện", "điểm cuối" hoặc "khác".
hướng (chuỗi)"in" hoặc "out". Chữ "in" Chỉ đường dùng để thông báo cho thiết bị rằng
thiết bị sẽ gửi thông tin đến máy chủ. Mọi hoạt động giao tiếp trên cổng USB
đều do máy chủ khởi tạo, vì vậy hãy sử dụng giá trị "in" chuyển dữ liệu để cho phép thiết bị
gửi lại thông tin.
yêu cầu (số nguyên)Được xác định bởi giao thức của thiết bị.
giá trị (số nguyên)Được xác định bởi giao thức của thiết bị.
chỉ mục (số nguyên)Được xác định bởi giao thức của thiết bị.
độ dài (số nguyên)Chỉ được dùng khi hướng là "vào". Thông báo cho thiết bị rằng đây là lượng dữ liệu mà máy chủ lưu trữ đang mong đợi phản hồi.
dữ liệu (vùng đệm mảng)Được xác định bởi giao thức của thiết bị, bắt buộc khi hướng là "out".

Ví dụ:

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

Chuyển dữ liệu ISOCHRONOUS

Chuyển đồng thời là loại chuyển USB phức tạp nhất. Các thẻ này thường được dùng cho sự kiện phát trực tiếp như video và âm thanh. Để bắt đầu quá trình chuyển đồng bộ (trong hoặc đi), bạn phải sử dụng phương thức usb.isochronousTransfer:

chrome.usb.isochronousTransfer(connectionHandle, isochronousTransferInfo, transferCallback)
Thông sốMô tả
connectionHandleĐã nhận được đối tượng trong lệnh gọi lại usb.openDevice.
isochronousTransferInfoĐối tượng thông số với các giá trị trong bảng bên dưới.
transferCallback()Được gọi khi quá trình chuyển hoàn tất.

Giá trị cho đối tượng isochronousTransferInfo:

Giá trịMô tả
TransferInfo (đối tượng)Một đối tượng có các thuộc tính sau:
hướng (chuỗi): "in" hoặc "out".
điểm cuối (số nguyên): do thiết bị của bạn xác định. Thường thì bạn có thể tìm thấy bằng công cụ so sánh kỹ thuật số của USB, chẳng hạn như lsusb -v
length (số nguyên): chỉ dùng khi hướng là "in". Thông báo cho thiết bị rằng đây là lượng dữ liệu mà máy chủ đang mong đợi để phản hồi.
Phải ÍT NHẤT packets × packetLength.
dữ liệu (bộ đệm mảng): xác định bởi giao thức của thiết bị; chỉ được sử dụng khi hướng là "ra ngoài".
gói (số nguyên)Tổng số gói dự kiến trong quá trình chuyển này.
PacketLength (số nguyên)Độ dài dự kiến của mỗi gói trong quá trình chuyển này.

Ví dụ:

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

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

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

Chuyển hàng loạt

Chuyển hàng loạt thường được dùng để chuyển một lượng lớn dữ liệu không có giới hạn thời gian theo cách đáng tin cậy . usb.bulkTransfer có 3 tham số:

chrome.usb.bulkTransfer(connectionHandle, transferInfo, transferCallback);
Thông sốMô tả
connectionHandleĐã nhận được đối tượng trong lệnh gọi lại usb.openDevice.
transferInfoĐối tượng thông số với các giá trị trong bảng bên dưới.
transferCallbackĐược gọi khi quá trình chuyển hoàn tất.

Giá trị cho đối tượng transferInfo:

Giá trịMô tả
hướng (chuỗi)"in" hoặc "out".
điểm cuối (số nguyên)Được xác định bởi giao thức của thiết bị.
độ dài (số nguyên)Chỉ được dùng khi hướng là "vào". Thông báo cho thiết bị rằng đây là lượng dữ liệu mà máy chủ lưu trữ đang mong đợi phản hồi.
dữ liệu (ArrayBuffer)Được xác định bằng giao thức của thiết bị; chỉ được sử dụng khi hướng là "ra ngoài".

Ví dụ:

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

TẠM DỪNG chuyển giao

Phương thức chuyển dữ liệu bị gián đoạn được dùng cho một lượng nhỏ dữ liệu nhạy cảm về thời gian. Vì tất cả giao tiếp USB đều do máy chủ khởi tạo, mã máy chủ thường thăm dò thiết bị theo định kỳ, gửi gián đoạn vào những lượt chuyển giúp thiết bị gửi dữ liệu trở lại nếu có bất cứ điều gì trong hàng đợi gián đoạn (được thiết bị duy trì). usb.interruptTransfer có 3 thông số:

chrome.usb.interruptTransfer(connectionHandle, transferInfo, transferCallback);
Thông sốMô tả
connectionHandleĐã nhận được đối tượng trong lệnh gọi lại usb.openDevice.
transferInfoĐối tượng thông số với các giá trị trong bảng bên dưới.
transferCallbackĐược gọi khi quá trình chuyển hoàn tất. Lưu ý rằng lệnh gọi lại này không chứa phản hồi của thiết bị. Mục đích của lệnh gọi lại chỉ là để thông báo cho mã của bạn rằng các yêu cầu chuyển không đồng bộ đã được xử lý.

Giá trị cho đối tượng transferInfo:

Giá trịMô tả
hướng (chuỗi)"in" hoặc "out".
điểm cuối (số nguyên)Được xác định bởi giao thức của thiết bị.
độ dài (số nguyên)Chỉ được dùng khi hướng là "vào". Thông báo cho thiết bị rằng đây là lượng dữ liệu mà máy chủ lưu trữ đang mong đợi phản hồi.
dữ liệu (ArrayBuffer)Được xác định bằng giao thức của thiết bị; chỉ được sử dụng khi hướng là "ra ngoài".

Ví dụ:

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

Chú ý

Không phải thiết bị nào cũng truy cập được qua USB API. Nói chung, bạn không thể truy cập vào thiết bị vì nhân của hệ điều hành hoặc trình điều khiển gốc giữ chúng khỏi mã không gian của người dùng. Hơi nhiều ví dụ như các thiết bị có hồ sơ HID trên hệ thống OSX và ổ đĩa bút USB.

Trên hầu hết các hệ thống Linux, theo mặc định, các thiết bị USB được liên kết với quyền chỉ có thể đọc. Để mở một thiết bị thông qua API này, người dùng của bạn cũng sẽ cần có quyền ghi vào thiết bị đó. Một giải pháp đơn giản là đặt một quy tắc udev. Tạo tệp /etc/udev/rules.d/50-yourdevicename.rules bằng các thành phần sau nội dung:

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

Sau đó, chỉ cần khởi động lại trình nền udev: service udev restart. Bạn có thể kiểm tra xem các quyền trên thiết bị có đang đặt chính xác bằng cách làm theo các bước sau:

  • Chạy lsusb để tìm số xe buýt và thiết bị.
  • Chạy ls -al /dev/bus/usb/[bus]/[device]. Tệp này phải thuộc sở hữu của nhóm "plugin" và có quyền ghi nhóm.

Ứng dụng của bạn không thể tự động thực hiện vì quy trình này yêu cầu quyền truy cập thư mục gốc. Bạn nên bạn cung cấp hướng dẫn cho người dùng cuối và liên kết đến mục Lưu ý trên trang này để nội dung giải thích.

Trên ChromeOS, bạn chỉ cần gọi usb.requestAccess. Nhà môi giới quyền sẽ thực hiện việc này cho bạn.