Kết nối với các thiết bị HID không phổ biến

WebHID API cho phép các trang web truy cập vào bàn phím phụ thay thế và các tay điều khiển trò chơi độc đáo.

François Beaufort
François Beaufort

Có một lượng dài các thiết bị giao diện con người (HID), chẳng hạn như bàn phím hoặc các tay điều khiển trò chơi độc đáo, quá mới, quá cũ hoặc quá hiếm gặp mà hệ thống có thể truy cập trình điều khiển thiết bị. API WebHID giải quyết vấn đề này bằng cách cung cấp một để triển khai logic theo thiết bị cụ thể trong JavaScript.

Các trường hợp sử dụng được đề xuất

Thiết bị HID lấy dữ liệu đầu vào hoặc cung cấp đầu ra cho con người. Ví dụ về thiết bị bao gồm bàn phím, thiết bị trỏ (chuột, màn hình cảm ứng, v.v.) và tay điều khiển trò chơi. Giao thức HID giúp bạn có thể truy cập vào những thiết bị này trên máy tính máy tính sử dụng trình điều khiển hệ điều hành. Nền tảng web hỗ trợ các thiết bị HID bằng cách dựa vào các trình điều khiển này.

Việc không thể tiếp cận các thiết bị HID không phổ biến đặc biệt đau đớn nói đến bàn phím phụ thay thế (ví dụ: Elgato Stream Deck, Jabra tai nghe, phím X) và khả năng hỗ trợ tay điều khiển trò chơi độc đáo. Tay chơi được thiết kế cho máy tính thường sử dụng HID cho đầu vào của tay điều khiển trò chơi (nút, cần điều khiển, bộ kích hoạt) và đầu ra (đèn LED, tiếng ầm ầm). Rất tiếc, đầu vào và đầu ra của tay điều khiển trò chơi không tốt các trình duyệt web được chuẩn hoá và thường yêu cầu logic tuỳ chỉnh cho các thiết bị cụ thể. Điều này không bền vững và dẫn đến sự hỗ trợ kém cho những nhóm người lớn tuổi và các thiết bị không phổ biến. Điều này cũng khiến trình duyệt phụ thuộc vào các điều lạ trong hành vi của các thiết bị cụ thể.

Thuật ngữ

HID bao gồm hai khái niệm cơ bản: báo cáo và chỉ số mô tả báo cáo. Báo cáo là dữ liệu được trao đổi giữa thiết bị và ứng dụng phần mềm. Phần mô tả báo cáo mô tả định dạng và ý nghĩa của dữ liệu mà thiết bị Google Cloud.

HID (Thiết bị giao diện con người) là một loại thiết bị nhận đầu vào từ hoặc cung cấp đầu ra cho con người. Nó cũng đề cập đến giao thức HID, một tiêu chuẩn cho hoạt động giao tiếp hai chiều giữa máy chủ và một thiết bị được thiết kế để đơn giản hoá quy trình cài đặt. Giao thức HID được phát triển ban đầu cho thiết bị USB, nhưng đã được triển khai trên nhiều giao thức khác, bao gồm cả Bluetooth.

Các ứng dụng và thiết bị HID trao đổi dữ liệu nhị phân thông qua 3 loại báo cáo:

Loại báo cáo Mô tả
Nhập báo cáo Dữ liệu được gửi từ thiết bị đến ứng dụng (ví dụ: nhấn nút).
Báo cáo kết quả Dữ liệu được gửi từ ứng dụng đến thiết bị (ví dụ: yêu cầu bật đèn nền bàn phím).
Báo cáo về tính năng Dữ liệu có thể được gửi theo một trong hai hướng. Định dạng dành riêng cho từng thiết bị.

Phần mô tả báo cáo mô tả định dạng nhị phân của báo cáo được hỗ trợ bởi thiết bị. Cấu trúc của báo cáo này mang tính phân cấp và có thể nhóm các báo cáo lại với nhau thành dạng riêng biệt các bộ sưu tập trong bộ sưu tập cấp cao nhất. Định dạng của phần mô tả là được xác định theo thông số kỹ thuật HID.

Cách sử dụng HID là một giá trị số tham chiếu đến dữ liệu đầu vào hoặc đầu ra được chuẩn hoá. Giá trị sử dụng cho phép một thiết bị mô tả mục đích sử dụng của thiết bị đó và mục đích của từng trường trong báo cáo. Ví dụ: một thuộc tính được xác định cho bên trái nút chuột. Việc sử dụng cũng được sắp xếp thành các trang sử dụng, cung cấp chỉ báo về danh mục cấp cao của thiết bị hoặc báo cáo.

Sử dụng API WebHID

Phát hiện tính năng

Để kiểm tra xem API WebHID có được hỗ trợ hay không, hãy dùng:

if ("hid" in navigator) {
  // The WebHID API is supported.
}

Mở kết nối HID

API WebHID không đồng bộ theo thiết kế để ngăn giao diện người dùng của trang web chặn khi đang chờ thông tin đầu vào. Việc này rất quan trọng vì ứng dụng có thể nhận được dữ liệu HID bất cứ lúc nào, cần có cách lắng nghe.

Để mở kết nối HID, trước tiên hãy truy cập vào một đối tượng HIDDevice. Để làm được điều này, bạn có thể nhắc người dùng chọn một thiết bị bằng cách gọi navigator.hid.requestDevice() hoặc chọn một địa điểm trong số navigator.hid.getDevices() Phương thức này trả về danh sách thiết bị mà trang web đã được cấp quyền truy cập trước đây.

Hàm navigator.hid.requestDevice() nhận một đối tượng bắt buộc xác định bộ lọc. Các số đó dùng để khớp với bất kỳ thiết bị nào kết nối với nhà cung cấp USB mã nhận dạng (vendorId), mã nhận dạng sản phẩm USB (productId), trang sử dụng (usagePage) và giá trị sử dụng (usage). Bạn có thể lấy những thẻ đó từ Kho lưu trữ mã nhận dạng USBtài liệu về bảng sử dụng HID.

Nhiều đối tượng HIDDevice được hàm này trả về đại diện cho nhiều đối tượng Giao diện HID trên cùng một thiết bị thực.

// Filter on devices with the Nintendo Switch Joy-Con USB Vendor/Product IDs.
const filters = [
  {
    vendorId: 0x057e, // Nintendo Co., Ltd
    productId: 0x2006 // Joy-Con Left
  },
  {
    vendorId: 0x057e, // Nintendo Co., Ltd
    productId: 0x2007 // Joy-Con Right
  }
];

// Prompt user to select a Joy-Con device.
const [device] = await navigator.hid.requestDevice({ filters });
// Get all devices the user has previously granted the website access to.
const devices = await navigator.hid.getDevices();
Ảnh chụp màn hình lời nhắc thiết bị HID trên một trang web.
Lời nhắc người dùng chọn Nintendo Switch Joy-Con.

Bạn cũng có thể sử dụng khoá exclusionFilters không bắt buộc trong navigator.hid.requestDevice() để loại trừ một số thiết bị khỏi bộ chọn của trình duyệt được xác định là hoạt động sai cách chẳng hạn.

// Request access to a device with vendor ID 0xABCD. The device must also have
// a collection with usage page Consumer (0x000C) and usage ID Consumer
// Control (0x0001). The device with product ID 0x1234 is malfunctioning.
const [device] = await navigator.hid.requestDevice({
  filters: [{ vendorId: 0xabcd, usagePage: 0x000c, usage: 0x0001 }],
  exclusionFilters: [{ vendorId: 0xabcd, productId: 0x1234 }],
});

Đối tượng HIDDevice chứa mã nhận dạng sản phẩm và nhà cung cấp USB của thiết bị nhận dạng. Thuộc tính collections của thuộc tính này được khởi tạo bằng mã phân cấp nội dung mô tả các định dạng báo cáo của thiết bị.

for (let collection of device.collections) {
  // An HID collection includes usage, usage page, reports, and subcollections.
  console.log(`Usage: ${collection.usage}`);
  console.log(`Usage page: ${collection.usagePage}`);

  for (let inputReport of collection.inputReports) {
    console.log(`Input report: ${inputReport.reportId}`);
    // Loop through inputReport.items
  }

  for (let outputReport of collection.outputReports) {
    console.log(`Output report: ${outputReport.reportId}`);
    // Loop through outputReport.items
  }

  for (let featureReport of collection.featureReports) {
    console.log(`Feature report: ${featureReport.reportId}`);
    // Loop through featureReport.items
  }

  // Loop through subcollections with collection.children
}

Theo mặc định, các thiết bị HIDDevice sẽ được trả về ở trạng thái "đóng" và phải là được mở bằng cách gọi open() trước khi gửi hoặc nhận dữ liệu.

// Wait for the HID connection to open before sending/receiving data.
await device.open();

Nhận báo cáo đầu vào

Sau khi thiết lập kết nối HID, bạn có thể xử lý nguồn đầu vào đến báo cáo bằng cách theo dõi các sự kiện "inputreport" từ thiết bị. Những sự kiện đó chứa dữ liệu HID dưới dạng đối tượng DataView (data), thiết bị HID thuộc về đến (device) và mã báo cáo 8 bit được liên kết với báo cáo đầu vào (reportId).

Ảnh nút chuyển nintendo màu đỏ và màu xanh dương.
Thiết bị Nintendo Switch Joy-Con.

Tiếp tục với ví dụ trước, mã bên dưới cho bạn biết cách phát hiện nút mà người dùng đã nhấn trên thiết bị Joy-Con bên phải để bạn có thể hy vọng bạn có thể thử tại nhà.

device.addEventListener("inputreport", event => {
  const { data, device, reportId } = event;

  // Handle only the Joy-Con Right device and a specific report ID.
  if (device.productId !== 0x2007 && reportId !== 0x3f) return;

  const value = data.getUint8(0);
  if (value === 0) return;

  const someButtons = { 1: "A", 2: "X", 4: "B", 8: "Y" };
  console.log(`User pressed button ${someButtons[value]}.`);
});

Gửi báo cáo đầu ra

Để gửi báo cáo đầu ra tới một thiết bị HID, hãy truyền mã báo cáo 8 bit được liên kết với báo cáo đầu ra (reportId) và byte dưới dạng BufferSource (data) để device.sendReport(). Lời hứa được trả về sẽ được giải quyết sau khi báo cáo được xử lý đã gửi. Nếu thiết bị HID không sử dụng mã báo cáo, hãy đặt reportId thành 0.

Ví dụ bên dưới áp dụng cho thiết bị Joy-Con và cho bạn biết cách với các báo cáo đầu ra.

// First, send a command to enable vibration.
// Magical bytes come from https://github.com/mzyy94/joycon-toolweb
const enableVibrationData = [1, 0, 1, 64, 64, 0, 1, 64, 64, 0x48, 0x01];
await device.sendReport(0x01, new Uint8Array(enableVibrationData));

// Then, send a command to make the Joy-Con device rumble.
// Actual bytes are available in the sample below.
const rumbleData = [ /* ... */ ];
await device.sendReport(0x10, new Uint8Array(rumbleData));

Gửi và nhận báo cáo tính năng

Báo cáo tính năng là loại báo cáo dữ liệu HID duy nhất có thể di chuyển ở cả hai đường đi. Chúng cho phép các thiết bị và ứng dụng HID trao đổi những thiết bị không được chuẩn hoá Dữ liệu HID. Không giống như báo cáo đầu vào và đầu ra, báo cáo tính năng sẽ không nhận được hoặc mà ứng dụng gửi thường xuyên.

Ảnh máy tính xách tay màu đen và bạc.
Bàn phím máy tính xách tay

Để gửi báo cáo tính năng tới một thiết bị HID, hãy truyền mã báo cáo 8 bit được liên kết với báo cáo tính năng (reportId) và byte dưới dạng BufferSource (data) để device.sendFeatureReport(). Lời hứa được trả về sẽ được xử lý sau khi báo cáo được xử lý đã được gửi. Nếu thiết bị HID không sử dụng mã báo cáo, hãy đặt reportId thành 0.

Ví dụ bên dưới minh hoạ cách sử dụng báo cáo tính năng bằng cách cho bạn biết cách hãy yêu cầu một thiết bị đèn nền bàn phím Apple, mở thiết bị đó và nhấp nháy thiết bị đó.

const waitFor = duration => new Promise(r => setTimeout(r, duration));

// Prompt user to select an Apple Keyboard Backlight device.
const [device] = await navigator.hid.requestDevice({
  filters: [{ vendorId: 0x05ac, usage: 0x0f, usagePage: 0xff00 }]
});

// Wait for the HID connection to open.
await device.open();

// Blink!
const reportId = 1;
for (let i = 0; i < 10; i++) {
  // Turn off
  await device.sendFeatureReport(reportId, Uint32Array.from([0, 0]));
  await waitFor(100);
  // Turn on
  await device.sendFeatureReport(reportId, Uint32Array.from([512, 0]));
  await waitFor(100);
}

Để nhận báo cáo tính năng từ thiết bị HID, hãy truyền mã báo cáo 8 bit được liên kết với báo cáo tính năng (reportId) để device.receiveFeatureReport() Lời hứa được trả về được xử lý bằng một Đối tượng DataView chứa nội dung của báo cáo tính năng. Nếu HID thiết bị không sử dụng mã báo cáo, hãy đặt reportId thành 0.

// Request feature report.
const dataView = await device.receiveFeatureReport(/* reportId= */ 1);

// Read feature report contents with dataView.getInt8(), getUint8(), etc...

Nghe thông báo kết nối và ngắt kết nối

Khi đã được cấp quyền truy cập vào một thiết bị HID, trang web có thể chủ động nhận các sự kiện kết nối và ngắt kết nối bằng cách lắng nghe "connect""disconnect" sự kiện.

navigator.hid.addEventListener("connect", event => {
  // Automatically open event.device or warn user a device is available.
});

navigator.hid.addEventListener("disconnect", event => {
  // Remove |event.device| from the UI.
});

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

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

Việc gọi forget() trên một thực thể HIDDevice sẽ thu hồi quyền truy cập vào tất cả giao diện HID trên cùng một thiết bị thực.

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

forget() có trong Chrome 100 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 ("hid" in navigator && "forget" in HIDDevice.prototype) {
  // forget() is supported.
}

Mẹo dành cho nhà phát triển

Gỡ lỗi HID trong Chrome thật dễ dàng 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ị HID và USB ở cùng một nơi.

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

Hãy xem Trình khám phá HID để kết xuất thiết bị HID thông tin sang định dạng mà con người có thể đọc được. Nó liên kết từ các giá trị sử dụng đến tên của mỗi Sử dụng HID.

Trên hầu hết các hệ thống Linux, thiết bị HID được ánh xạ với quyền chỉ đọc bằng cách mặc định. Để cho phép Chrome mở thiết bị HID, 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:

KERNEL=="hidraw*", ATTRS{idVendor}=="[yourdevicevendor]", MODE="0664", GROUP="plugdev"

Trong dòng trên, [yourdevicevendor]057e nếu thiết bị của bạn là Nintendo Switch Joy-Con chẳng hạn. Bạn cũng có thể thêm ATTRS{idProduct} để có trải nghiệm cụ thể hơn . Hãy đảm bảo user của bạn là thành viên trong nhóm plugdev. Sau đó, chỉ cần kết nối lại thiết bị.

Hỗ trợ trình duyệt

WebHID API có trên tất cả các nền tảng dành cho máy tính (ChromeOS, Linux, macOS, và Windows) trong Chrome 89.

Bản thu thử

Một số bản minh hoạ WebHID được liệt kê tại web.dev/hid-examples. Hãy khám phá!

Bảo mật và quyền riêng tư

Các tác giả của thông số kỹ thuật này đã thiết kế và triển khai API WebHID bằng cách sử dụng nguyên tắc xác định trong Kiểm soát quyền truy cập vào các tính năng nền tảng web mạnh mẽ, bao gồm cả quyền kiểm soát của người dùng, tính minh bạch và công thái học. Khả năng sử dụng tính năng này API chủ yếu chịu sự kiểm soát của một mô hình quản lý quyền chỉ cấp quyền truy cập vào một tài khoản duy nhất thiết bị HID cùng một lúc. Để phản hồi lời nhắc của người dùng, người dùng phải kích hoạt để chọn một thiết bị HID cụ thể.

Để hiểu rõ các đánh đổi về bảo mật, hãy xem bài viết Bảo mật và quyền riêng tư Phần Những điều cần cân nhắc trong quy cách WebHID.

Ngoài ra, Chrome sẽ kiểm tra việc sử dụng từng tập hợp cấp cao nhất và xem tập hợp cấp cao nhất có cách sử dụng được bảo vệ (ví dụ: bàn phím chung, chuột), thì một trang web sẽ không thể gửi và nhận bất kỳ báo cáo nào được xác định trong bộ sưu tập. Danh sách đầy đủ các trường hợp sử dụng được bảo vệ đã được cung cấp công khai.

Xin lưu ý rằng các thiết bị HID nhạy cảm về bảo mật (chẳng hạn như thiết bị FIDO HID được dùng cho xác thực mạnh hơn) cũng bị chặn trong Chrome. Xem danh sách chặn USB và Tệp danh sách chặn HID.

Phản hồi

Nhóm Chrome rất muốn biết suy nghĩ và trải nghiệm của bạn với API WebHID.

Cho chúng tôi biết về thiết kế API

Có điều gì về API không hoạt động như mong đợi không? Hoặc có còn thiếu phương thức hoặc thuộc tính nào mà bạn cần để triển khai ý tưởng của mình?

Gửi vấn đề về thông số kỹ thuật trên kho lưu trữ GitHub API WebHID hoặc thêm ý kiến của bạn cho vấn đề hiện tại.

Báo cáo sự cố về triển khai

Bạn có phát hiện lỗi trong quá trình triển khai Chrome không? Hoặc là triển khai khác với thông số kỹ thuật không?

Hãy tham khảo bài viết Cách gửi lỗi WebHID. Hãy nhớ bao gồm nhiều chi tiết nhất có thể, cung cấp hướng dẫn đơn giản để tái tạo lỗi và Thành phần được đặt thành Blink>HID. Lỗi trục phù hợp với chia sẻ các bản ghi lại nhanh chóng và dễ dàng.

Thể hiện sự ủng hộ

Bạn có dự định sử dụng API WebHID không? Sự hỗ trợ công khai của bạn sẽ giúp Chrome nhóm ưu tiên các tính năng và cho các nhà cung cấp trình duyệt khác biết tầm quan trọng của việc này hỗ trợ họ.

Gửi một bài đăng đến @ChromiumDev kèm theo hashtag #WebHID và cho chúng tôi biết vị trí và cách bạn sử dụng công cụ đó.

Các đường liên kết hữu ích

Xác nhận

Cảm ơn Matt ReynoldsJoe Medley đã đánh giá bài viết này. Ảnh chụp Nintendo Switch màu đỏ và xanh dương của Sara Kurfeß cùng chiếc máy tính xách tay màu đen và bạc ảnh máy tính của Athul Cyriac Ajay trên Unsplash.