Thiết bị USB

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

Để biết thông tin cơ bản về USB, hãy xem thông số kỹ thuật của USB chính thức. USB trong NutShell là một khoá học cấp tốc 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 quyền "usb" trong tệp kê khai:

"permissions": [
  "usb"
]

Ngoài ra, để ngăn vân tay, bạn phải khai báo mọi loại thiết bị mà bạn 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 cặp mã sản phẩm/mã nhà cung 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.

Bạn phải khai báo các cặp VID/PID cho từng loại thiết bị mà mình muốn sử dụng theo quyền usbDevices 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, yêu cầu khai báo tất cả các loại thiết bị trong tệp kê khai ứng dụng sẽ được nới lỏng đối với các ứng dụng chạy dưới dạng ứng dụng kiosk ChromeOS. Đối với các ứng dụng kiosk, bạn có thể dùng thuộc tính quyền interfaceClass để yêu cầu 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 loại thiết bị USB cụ thể

Ví dụ: quyền usbDevices sau đây sẽ cấp quyền truy cập cho ứng dụng vào tất cả các thiết bị USB triển khai giao diện máy in (mã lớp giao diện 7) và quyền truy cập vào các thiết bị trung tâm 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 Mã loại 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 vào các thiết bị USB 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 một 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 phương thức usb.getDevices:

chrome.usb.getDevices(enumerateDevicesOptions, callback);
Thông số (loại)Nội dung mô tả
EnumerateDevicesOptions (đối tượng)Một đối tượng chỉ định cả vendorId (long) và productId (long) dùng để tìm đúng loại thiết bị trên bus. 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 liệt kê thiết bị xong. Lệnh gọi lại sẽ được thực thi với một tham số là một mảng gồm các đối tượng Device có ba thuộc tính: device, vendorId, productId. Thuộc tính thiết bị là giá trị nhận dạng ổn định cho thiết bị đã kết nối. Chế độ này sẽ không thay đổi cho đến khi rút thiết bị ra khỏi nguồn điện. Chi tiết của giá trị nhận dạng là không rõ ràng và có thể thay đổi. Đừng dựa vào loại thiết bị hiện tại.
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 tên người dùng 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.

Tài sảnNội dung mô tả
thiết bịĐối tượng nhận được trong lệnh gọi lại usb.getDevices.
dữ liệu (bộ đệm mảng)Chứa dữ liệu mà thiết bị gửi nếu dữ liệu được chuyển đế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. Phương thức này sẽ liệt kê, yêu cầu quyền truy cập và mở các thiết bị trong một lệnh 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);
        }
      });
    });
  })
});

Chuyển và nhận dữ liệu qua USB từ thiết bị

Giao thức USB xác định 4 loại quá trình chuyển: kiểm soát, hàng loạt, đứng thờigián đoạn. Những quy trình chuyển này được mô tả dưới đây.

Quá trình chuyển 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ị (đi). Do bản chất của giao thức USB, cả thư đến và đi đều phải do máy chủ bắt đầu (máy tính chạy ứng dụng Chrome). Đối với thông báo đến (từ thiết bị đến máy chủ), máy chủ lưu trữ (do mã JavaScript của bạn khởi tạo) sẽ gửi thông báo bị gắn cờ là "gửi đến" tới thiết bị. Chi tiết của thông báo tuỳ 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ừ thiết bị đó. 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 Chrome xử lý và được phân phối không đồng bộ đến lệnh gọi lại mà bạn chỉ định trong phương thức chuyển. Thông báo đi (từ máy chủ đến thiết bị) cũng tương tự như vậy, nhưng phản hồi không chứa dữ liệu mà 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 được một đối tượng sự kiện có các thuộc tính sau:

Tài sảnNội dung mô tả
kết quả mã (số nguyên)0 là thành công; các giá trị khác biểu thị không thành công. Hệ thống có thể
đọc một chuỗi lỗi từ chrome.extension.lastError khi hệ thống chỉ báo lỗi
.
dữ liệu (bộ đệm mảng)Chứa dữ liệu mà thiết bị gửi nếu dữ liệu được chuyển đế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

Chuyển quyền điều khiển thường được dùng để gửi hoặc nhận các tham số lệnh hoặc cấu hình tới thiết bị USB. Phương thức ControlTransfer luôn gửi đến/đọc từ điểm cuối 0 và không cần có claimInterface. 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)Nội dung mô tả
connectionHandleĐối tượng đã nhận được trong lệnh gọi lại usb.openDevice.
transferInfoĐối tượng tham số có các giá trị trong bảng bên dưới. Hãy kiểm tra thông số kỹ thuật của giao thức thiết bị USB để biết chi tiết.
transferCallback()Được gọi khi quá trình chuyển hoàn tất.

Giá trị của đối tượng transferInfo:

Giá trịNội dung mô tả
requestType (chuỗi)"nhà cung cấp", "tiêu chuẩn", "lớp" 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)"vào" hoặc "ra". Hướng "trong" được 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 xe buýt
USB đều do máy chủ khởi tạo, vì vậy, hãy sử dụng quy trình chuyển "trong" để cho phép thiết bị
gửi lại thông tin.
yêu cầu (số nguyên)Do giao thức của thiết bị của bạn xác định.
giá trị (số nguyên)Do giao thức của thiết bị của bạn xác định.
chỉ mục (số nguyên)Do giao thức của thiết bị của bạn xác định.
độ dài (số nguyên)Chỉ được dùng khi hướng là "trong". Thông báo cho thiết bị rằng đây là lượng dữ liệu mà máy chủ lưu trữ mong muốn phản hồi.
dữ liệu (bộ đệm mảng)Do giao thức của thiết bị xác định, bắt buộc khi hướng là "ra".

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 từ ISOCHRONOUS

Chuyển không đồng thời là loại chuyển USB phức tạp nhất. Chúng thường được dùng cho các luồng dữ liệu, như video và âm thanh. Để bắt đầu quá trình chuyển theo thời gian (đến 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ốNội dung mô tả
connectionHandleĐối tượng đã nhận được trong lệnh gọi lại usb.openDevice.
isochronousTransferInfoĐối tượng tham số có 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ị của đối tượng isochronousTransferInfo:

Giá trịNội dung mô tả
thông tin chuyển (đố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. Bạn thường có thể tìm thấy bằng cách xem công cụ dự đoán USB, chẳng hạn như lsusb -v
độ dài (số nguyên): chỉ dùng khi hướng là "trong". Thông báo cho thiết bị rằng đây là lượng dữ liệu mà máy chủ dự kiến phản hồi.
Phải ít nhất packets × packetLength.
dữ liệu (vùng đệm mảng): do giao thức của thiết bị xác định; chỉ được dùng khi hướng là "ra".
gói (số nguyên)Tổng số gói dự kiến trong quá trình chuyển này.
góietLength (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

Tính năng 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 về thời gian bằng một cách đáng tin cậy. usb.bulkTransfer có ba tham số:

chrome.usb.bulkTransfer(connectionHandle, transferInfo, transferCallback);
Thông sốNội dung mô tả
connectionHandleĐối tượng đã nhận được trong lệnh gọi lại usb.openDevice.
transferInfoĐối tượng tham số có 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ị của đối tượng transferInfo:

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

Ví dụ:

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

interRUPT chuyển tuyến

Tính năng chuyển bị gián đoạn được dùng đối với lượng nhỏ dữ liệu có giới hạn về thời gian. Vì mọi hoạt động giao tiếp qua USB đều do máy chủ khởi tạo, nên mã máy chủ thường thăm dò thiết bị theo định kỳ, khi gửi các hoạt động chuyển IN gây gián đoạn khiến thiết bị gửi lại dữ liệu nếu có bất kỳ hoạt động nào trong hàng đợi gián đoạn (do thiết bị duy trì). usb.interruptTransfer có 3 tham số:

chrome.usb.interruptTransfer(connectionHandle, transferInfo, transferCallback);
Thông sốNội dung mô tả
connectionHandleĐối tượng đã nhận được trong lệnh gọi lại usb.openDevice.
transferInfoĐối tượng tham số có 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. Xin 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ỉ để 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ị của đối tượng transferInfo:

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

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 có thể truy cập được thông qua API USB. Nhìn chung, các thiết bị không truy cập được vì nhân của hệ điều hành hoặc trình điều khiển gốc ngăn các thiết bị đó đọc mã không gian của người dùng. Một số ví dụ là các thiết bị có cấu hình HID trên hệ thống OSX và ổ đĩa USB dạng bút.

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

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

Sau đó, bạn 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ó được thiết lập chính xác hay không bằng cách làm theo các bước sau:

  • Chạy lsusb để tìm số xe buýt và số 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 "plugdev" và có quyền ghi nhóm.

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

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