Truy cập thiết bị USB trên web

WebUSB API giúp USB an toàn và dễ sử dụng hơn bằng cách đưa ứng dụng này lên web.

François Beaufort
François Beaufort

Nếu tôi nói rõ ràng và đơn giản là "USB", thì có khả năng bạn sẽ nghĩ ngay đến bàn phím, chuột, âm thanh, video và thiết bị lưu trữ. Bạn đúng, nhưng bạn sẽ tìm thấy các loại thiết bị Universal Serial Bus (USB) khác ở đó.

Những thiết bị USB không chuẩn hoá này yêu cầu các nhà cung cấp phần cứng phải ghi dữ liệu dành riêng cho từng nền tảng trình điều khiển và SDK để bạn (nhà phát triển) tận dụng các trình điều khiển đó. Rất tiếc, mã dành riêng cho nền tảng này trước đây đã ngăn không cho các thiết bị này sử dụng trên web. Và đó là một trong những lý do khiến WebUSB API ra đời: để cung cấp cách hiển thị dịch vụ thiết bị USB lên Web. Với API này, phần cứng nhà sản xuất có thể tạo SDK JavaScript đa nền tảng cho thiết bị.

Nhưng quan trọng nhất là việc này sẽ giúp USB an toàn và dễ sử dụng hơn nhờ mang ứng dụng lên Web.

Hãy xem hành vi bạn có thể mong đợi với API WebUSB:

  1. Mua thiết bị USB.
  2. Cắm thiết bị vào máy tính. Một thông báo sẽ xuất hiện ngay lập tức ở bên phải truy cập trên trang web nào cho thiết bị này.
  3. Nhấp vào thông báo đó. Trang web đã sẵn sàng để bạn sử dụng!
  4. Nhấp để kết nối và trình chọn thiết bị USB sẽ hiển thị trong Chrome nơi bạn có thể chọn thiết bị của bạn.

Tada!

Quy trình này sẽ như thế nào nếu không có API WebUSB?

  1. Cài đặt ứng dụng dành riêng cho nền tảng.
  2. Nếu tệp này thậm chí còn được hỗ trợ trên hệ điều hành của tôi, hãy xác minh rằng tôi đã tải xuống nội dung phù hợp.
  3. Cài đặt. Nếu may mắn, bạn sẽ không nhận được cửa sổ bật lên hay lời nhắc hệ điều hành đáng sợ nào cảnh báo bạn về việc cài đặt trình điều khiển/ứng dụng qua Internet. Nếu bạn không may mắn, trình điều khiển hoặc ứng dụng đã cài đặt sẽ gặp trục trặc và gây hại máy tính của bạn. (Hãy nhớ rằng web được xây dựng để ngăn chặn những trục trặc ).
  4. Nếu bạn chỉ sử dụng tính năng này một lần, mã sẽ được lưu lại trên máy tính cho đến khi bạn hãy nghĩ đến việc xoá nội dung đó. (Trên web, không gian chưa được sử dụng cuối cùng sẽ là reclaimed.)

Trước khi tôi bắt đầu

Bài viết này giả định bạn đã có một số kiến thức cơ bản về cách hoạt động của USB. Nếu không, tôi bạn nên đọc USB trong NutShell. Để 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.

API WebUSB có trong Chrome 61.

Có cho bản dùng thử theo nguyên gốc

Để nhận được nhiều phản hồi nhất có thể từ các nhà phát triển sử dụng WebUSB API mà chúng tôi đề cập, trước đây chúng tôi đã thêm tính năng này vào Chrome 54 và Chrome 57 dưới dạng bản dùng thử theo nguyên gốc.

Thử nghiệm mới nhất đã kết thúc thành công vào tháng 9 năm 2017.

Quyền riêng tư và bảo mật

Chỉ HTTPS

Do sức mạnh của tính năng này nên tính năng chỉ hoạt động trên bối cảnh an toàn. Điều này có nghĩa là bạn sẽ phải tạo giao thức có lưu ý đến TLS.

Yêu cầu cử chỉ của người dùng

Để đề phòng vấn đề bảo mật, navigator.usb.requestDevice() có thể chỉ được gọi thông qua một cử chỉ của người dùng, chẳng hạn như chạm hoặc nhấp chuột.

Chính sách về quyền

Chính sách về quyền là một cơ chế cho phép nhà phát triển chọn cấp quyền cũng như tắt nhiều tính năng và API của trình duyệt. Có thể xác định URL này qua HTTP tiêu đề và/hoặc iframe "cho phép" .

Bạn có thể xác định Chính sách về quyền kiểm soát việc thuộc tính usb có phải là hiển thị trên đối tượng Navigator hoặc nói cách khác nếu bạn cho phép WebUSB.

Dưới đây là ví dụ về một chính sách tiêu đề không cho phép WebUSB:

Feature-Policy: fullscreen "*"; usb "none"; payment "self" https://payment.example.com

Dưới đây là một ví dụ khác về chính sách vùng chứa cho phép USB:

<iframe allowpaymentrequest allow="usb; fullscreen"></iframe>

Hãy bắt đầu lập trình

WebUSB API chủ yếu dựa vào Promise (Lời hứa) của JavaScript. Nếu bạn không quen thuộc với họ, hãy xem hướng dẫn về các lời hứa tuyệt vời này. Một điều nữa, () => {} chỉ đơn giản là các hàm mũi tên ECMAScript 2015.

Truy cập vào thiết bị USB

Bạn có thể nhắc người dùng chọn một thiết bị USB đã kết nối bằng cách sử dụng navigator.usb.requestDevice() hoặc gọi navigator.usb.getDevices() để nhận danh sách tất cả thiết bị USB đã kết nối mà trang web đã được cấp quyền truy cập.

Hàm navigator.usb.requestDevice() nhận đối tượng JavaScript bắt buộc xác định filters. Các bộ lọc này được sử dụng để khớp bất kỳ thiết bị USB nào có nhà cung cấp (vendorId) và mã nhận dạng sản phẩm (productId) (không bắt buộc). Các khoá classCode, protocolCode, serialNumbersubclassCode có thể cũng được xác định ở đó.

Ảnh chụp màn hình lời nhắc người dùng thiết bị USB trong Chrome
Lời nhắc dành cho người dùng thiết bị USB.

Ví dụ: dưới đây là cách truy cập vào một thiết bị Arduino đã kết nối đã định cấu hình để cho phép nguồn gốc.

navigator.usb.requestDevice({ filters: [{ vendorId: 0x2341 }] })
.then(device => {
  console.log(device.productName);      // "Arduino Micro"
  console.log(device.manufacturerName); // "Arduino LLC"
})
.catch(error => { console.error(error); });

Trước khi bạn hỏi, tôi chưa từng nghĩ ra 0x2341 hệ thập lục phân này số. Tôi chỉ tìm kiếm từ "Arduino" trong Danh sách mã USB này.

USB device được trả về trong lời hứa đã thực hiện ở trên có một số tính năng cơ bản thông tin quan trọng về thiết bị, chẳng hạn như phiên bản USB được hỗ trợ, kích thước gói tối đa, mã nhà cung cấp và mã sản phẩm, số lượng gói tin có thể các cấu hình mà thiết bị có thể có. Về cơ bản, lớp này chứa tất cả các trường trong Trình mô tả USB của thiết bị.

// Get all connected USB devices the website has been granted access to.
navigator.usb.getDevices().then(devices => {
  devices.forEach(device => {
    console.log(device.productName);      // "Arduino Micro"
    console.log(device.manufacturerName); // "Arduino LLC"
  });
})

Nhân tiện, nếu thiết bị USB thông báo hỗ trợ WebUSB, cũng như xác định URL trang đích, Chrome sẽ hiển thị thông báo liên tục khi Đã cắm thiết bị USB. Khi bạn nhấp vào thông báo này, trang đích sẽ mở ra.

Ảnh chụp màn hình thông báo WebUSB trong Chrome
Thông báo WebUSB.

Nói chuyện với bo mạch USB Arduino

Được rồi, bây giờ hãy xem việc giao tiếp từ một thiết bị tương thích WebUSB dễ dàng như thế nào Bảng mạch Arduino trên cổng USB. Xem hướng dẫn tại https://github.com/webusb/arduino để WebUSB cho phép các bản phác thảo của bạn.

Đừng lo, tôi sẽ đề cập đến tất cả phương thức thiết bị WebUSB được đề cập bên dưới ở phần sau bài viết này.

let device;

navigator.usb.requestDevice({ filters: [{ vendorId: 0x2341 }] })
.then(selectedDevice => {
    device = selectedDevice;
    return device.open(); // Begin a session.
  })
.then(() => device.selectConfiguration(1)) // Select configuration #1 for the device.
.then(() => device.claimInterface(2)) // Request exclusive control over interface #2.
.then(() => device.controlTransferOut({
    requestType: 'class',
    recipient: 'interface',
    request: 0x22,
    value: 0x01,
    index: 0x02})) // Ready to receive data
.then(() => device.transferIn(5, 64)) // Waiting for 64 bytes of data from endpoint #5.
.then(result => {
  const decoder = new TextDecoder();
  console.log('Received: ' + decoder.decode(result.data));
})
.catch(error => { console.error(error); });

Xin lưu ý rằng thư viện WebUSB mà tôi đang sử dụng chỉ đang triển khai một giao thức mẫu (dựa trên giao thức nối tiếp USB chuẩn) và nhà sản xuất có thể tạo bất kỳ tập hợp và loại điểm cuối nào họ muốn. Chuyển quyền kiểm soát đặc biệt phù hợp với các lệnh cấu hình nhỏ như chúng được ưu tiên xe buýt và có cấu trúc được xác định rõ ràng.

Và đây là bản phác thảo đã được tải lên bảng Arduino.

// Third-party WebUSB Arduino library
#include <WebUSB.h>

WebUSB WebUSBSerial(1 /* https:// */, "webusb.github.io/arduino/demos");

#define Serial WebUSBSerial

void setup() {
  Serial.begin(9600);
  while (!Serial) {
    ; // Wait for serial port to connect.
  }
  Serial.write("WebUSB FTW!");
  Serial.flush();
}

void loop() {
  // Nothing here for now.
}

Thư viện WebUSB Arduino của bên thứ ba được sử dụng trong mã mẫu ở trên về cơ bản có hai điều:

  • Thiết bị này hoạt động như một thiết bị WebUSB cho phép Chrome đọc URL trang đích.
  • Thao tác này sẽ hiển thị một API WebUSB Serial mà bạn có thể dùng để ghi đè API mặc định.

Xem lại mã JavaScript. Sau khi nhận được device do người dùng chọn, device.open() thực hiện tất cả các bước dành riêng cho từng nền tảng để bắt đầu phiên bằng USB thiết bị. Sau đó, tôi phải chọn một Cấu hình USB có sẵn device.selectConfiguration(). Hãy nhớ rằng cấu hình chỉ định cách thiết bị được cấp nguồn, mức tiêu thụ điện năng tối đa và số lượng giao diện của thiết bị. Nói về giao diện, tôi cũng cần yêu cầu quyền truy cập độc quyền với device.claimInterface() vì dữ liệu chỉ có thể được chuyển sang giao diện hoặc các điểm cuối được liên kết khi giao diện được xác nhận quyền sở hữu. Cuối cùng cũng gọi được Cần có device.controlTransferOut() để thiết lập thiết bị Arduino bằng các lệnh thích hợp để giao tiếp qua WebUSB Serial API.

Tại đó, device.transferIn() sẽ thực hiện việc chuyển hàng loạt vào để thông báo rằng máy chủ lưu trữ đã sẵn sàng nhận dữ liệu hàng loạt. Sau đó, Hứa hẹn được thực hiện bằng đối tượng result chứa DataView data phải được phân tích cú pháp một cách thích hợp.

Nếu bạn đã quen thuộc với USB thì tất cả những tính năng này trông khá quen thuộc.

Tôi muốn nhiều hơn

WebUSB API cho phép bạn tương tác với tất cả các loại điểm cuối/truyền qua USB:

  • Kiểm soát các lệnh chuyển, dùng để gửi hoặc nhận cấu hình hoặc lệnh các tham số vào thiết bị USB, được xử lý bằng controlTransferIn(setup, length)controlTransferOut(setup, data).
  • Ngắt kết nối, dùng cho một lượng nhỏ dữ liệu có giới hạn thời gian, được xử lý bằng các phương thức tương tự như chuyển BULK bằng transferIn(endpointNumber, length)transferOut(endpointNumber, data).
  • Truyền ISOCHRONOUS, được sử dụng cho các luồng dữ liệu như video và âm thanh, được được xử lý với isochronousTransferIn(endpointNumber, packetLengths)isochronousTransferOut(endpointNumber, data, packetLengths)
  • nhiều lượt chuyển, được dùng để chuyển một lượng lớn dữ liệu không có giới hạn thời gian trong một cách đáng tin cậy, được xử lý bằng transferIn(endpointNumber, length)transferOut(endpointNumber, data)

Bạn cũng nên xem dự án WebLight của Mike Tsao cung cấp ví dụ từ đầu về cách xây dựng thiết bị LED được điều khiển bằng USB được thiết kế cho WebUSB API (không sử dụng Arduino tại đây). Bạn sẽ thấy phần cứng, phần mềm và chương trình cơ sở.

Thu hồi quyền truy cập vào thiết bị USB

Trang web này có thể dọn dẹp quyền truy cập vào thiết bị USB mà trang web không cần nữa bằng cách gọi forget() trên thực thể USBDevice. Ví dụ: để có một ứng dụng web giáo dục được dùng trên một máy tính dùng chung với nhiều thiết bị, số lượng quyền tích luỹ do người dùng tạo sẽ tạo ra trải nghiệm kém cho người dùng.

// Voluntarily revoke access to this USB device.
await device.forget();

forget() có trong Chrome 101 trở lên, hãy kiểm tra xem tính năng này có được hỗ trợ bằng các tính năng sau:

if ("usb" in navigator && "forget" in USBDevice.prototype) {
  // forget() is supported.
}

Giới hạn về số tiền chuyển

Một số hệ điều hành đặt ra giới hạn về lượng dữ liệu có thể được đưa vào các giao dịch USB đang chờ xử lý. Chia dữ liệu của bạn thành các giao dịch nhỏ hơn và chỉ gửi một vài thư cùng lúc sẽ giúp tránh những hạn chế đó. Đồng thời, mức bộ nhớ đã sử dụng và cho phép ứng dụng của bạn báo cáo tiến trình dưới dạng quá trình chuyển đã hoàn tất.

Do nhiều lượt chuyển được gửi tới một điểm cuối luôn thực thi theo thứ tự, nên có thể cải thiện thông lượng bằng cách gửi nhiều đoạn hàng đợi để tránh độ trễ giữa các lần chuyển USB. Mỗi khi một đoạn được truyền đầy đủ, thông báo cho mã của bạn biết rằng mã đó sẽ cung cấp thêm dữ liệu như được ghi trong trình trợ giúp ví dụ về hàm bên dưới.

const BULK_TRANSFER_SIZE = 16 * 1024; // 16KB
const MAX_NUMBER_TRANSFERS = 3;

async function sendRawPayload(device, endpointNumber, data) {
  let i = 0;
  let pendingTransfers = [];
  let remainingBytes = data.byteLength;
  while (remainingBytes > 0) {
    const chunk = data.subarray(
      i * BULK_TRANSFER_SIZE,
      (i + 1) * BULK_TRANSFER_SIZE
    );
    // If we've reached max number of transfers, let's wait.
    if (pendingTransfers.length == MAX_NUMBER_TRANSFERS) {
      await pendingTransfers.shift();
    }
    // Submit transfers that will be executed in order.
    pendingTransfers.push(device.transferOut(endpointNumber, chunk));
    remainingBytes -= chunk.byteLength;
    i++;
  }
  // And wait for last remaining transfers to complete.
  await Promise.all(pendingTransfers);
}

Mẹo

Gỡ lỗi USB trong Chrome dễ dàng hơn với trang nội bộ about://device-log nơi bạn có thể xem tất cả sự kiện liên quan đến thiết bị USB ở cùng một nơi.

Ảnh chụp màn hình trang nhật ký thiết bị để gỡ lỗi WebUSB trong Chrome
Trang nhật ký thiết bị trong Chrome để gỡ lỗi API WebUSB.

Trang nội bộ about://usb-internals cũng rất hữu ích và cho phép bạn để mô phỏng quá trình kết nối và ngắt kết nối các thiết bị WebUSB ảo. Điều này rất hữu ích khi kiểm thử giao diện người dùng mà không cần phần cứng thực.

Ảnh chụp màn hình trang nội bộ để gỡ lỗi WebUSB trong Chrome
Trang nội bộ trong Chrome để gỡ lỗi API WebUSB.

Trên hầu hết các hệ thống Linux, thiết bị USB được ánh xạ với quyền chỉ đọc bằng cách mặc định. Để cho phép Chrome mở thiết bị USB, bạn cần thêm một udev mới . Tạo một tệp tại /etc/udev/rules.d/50-yourdevicename.rules bằng phần tử nội dung sau:

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

trong đó [yourdevicevendor]2341 nếu thiết bị của bạn là Arduino. Bạn cũng có thể thêm ATTR{idProduct} cho quy tắc cụ thể hơn. Hãy đảm bảo rằng userthành viên của nhóm plugdev. Sau đó, bạn chỉ cần kết nối lại thiết bị.

Tài nguyên

Gửi một bài đăng đến @ChromiumDev kèm theo hashtag #WebUSB đồng thời cho chúng tôi biết bạn đang sử dụng ở đâu và như thế nào.

Xác nhận

Cảm ơn Joe Medley đã đánh giá bài viết này.