API WebHID cho phép các trang web truy cập vào bàn phím phụ thay thế và tay điều khiển trò chơi độc đáo.
Có một danh sách dài các thiết bị giao diện người dùng (HID), chẳng hạn như bàn phím thay thế hoặc tay điều khiển trò chơi lạ, quá mới, quá cũ hoặc quá hiếm để trình điều khiển thiết bị của hệ thống có thể truy cập. API WebHID giải quyết vấn đề này bằng cách cung cấp một cách để triển khai logic dành riêng cho thiết bị trong JavaScript.
Các trường hợp sử dụng được đề xuất
Thiết bị HID nhận dữ liệu đầu vào hoặc cung cấp dữ liệu đầ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 cho phép truy cập vào các thiết bị này trên máy tính để bàn bằ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ể truy cập vào các thiết bị HID không phổ biến đặc biệt gây phiền toái khi nói đến bàn phím phụ thay thế (ví dụ: Elgato Stream Deck, tai nghe Jabra, X-keys) và hỗ trợ tay điều khiển trò chơi lạ. Tay điều khiển trò chơi được thiết kế cho máy tính thường sử dụng HID cho đầu vào tay điều khiển trò chơi (nút, cần điều khiển, cò súng) và đầu ra (đèn LED, rung). Rất tiếc, đầu vào và đầu ra của tay điều khiển trò chơi không được chuẩn hoá tốt và trình duyệt web thường yêu cầu logic tuỳ chỉnh cho các thiết bị cụ thể. Điều này là không bền vững và dẫn đến việc hỗ trợ kém cho phần đuôi dài của các thiết bị cũ và không phổ biến. Điều này cũng khiến trình duyệt phụ thuộc vào các đặc điểm kỳ lạ trong hành vi của một số 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 một thiết bị và ứng dụng phần mềm. Chỉ số mô tả báo cáo mô tả định dạng và ý nghĩa của dữ liệu mà thiết bị hỗ trợ.
HID (Thiết bị giao diện người dùng) là một loại thiết bị nhận dữ liệu đầu vào hoặc cung cấp dữ liệu đầu ra cho con người. Tên này cũng đề cập đến giao thức HID, một tiêu chuẩn giao tiếp hai chiều giữa máy chủ và thiết bị được thiết kế để đơn giản hoá quy trình cài đặt. Giao thức HID ban đầu được phát triển cho các thiết bị USB, nhưng kể từ đó đã đượ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ả |
---|---|
Báo cáo đầu và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 đầu ra | 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 tính năng | Dữ liệu có thể được gửi theo một trong hai hướng. Định dạng này dành riêng cho từng thiết bị. |
Chỉ số mô tả báo cáo mô tả định dạng tệp nhị phân của báo cáo mà thiết bị hỗ trợ. Cấu trúc của báo cáo này có dạng phân cấp và có thể nhóm các báo cáo lại với nhau dưới dạng các tuyển tập riêng biệt trong tuyển tập cấp cao nhất. Định dạng của chỉ số mô tả được xác định theo thông số kỹ thuật HID.
Mức sử dụng HID là một giá trị số đề cập đến đầu vào hoặc đầu ra được chuẩn hoá. Giá trị sử dụng cho phép 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 nút được xác định cho nút chuột trái. Lượt sử dụng cũng được sắp xếp thành các trang sử dụng, cho biết 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 sử dụng:
if ("hid" in navigator) {
// The WebHID API is supported.
}
Mở kết nối HID
API WebHID được thiết kế không đồng bộ để ngăn giao diện người dùng trang web chặn khi chờ dữ liệu đầu vào. Điều này rất quan trọng vì dữ liệu HID có thể được nhận bất cứ lúc nào, đòi hỏi phải có cách để nghe dữ liệu đó.
Để mở kết nối HID, trước tiên, hãy truy cập vào đối tượng HIDDevice
. Để thực hiện việc 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 thiết bị trong navigator.hid.getDevices()
. Phương thức này sẽ trả về danh sách các thiết bị mà trang web đã được cấp quyền truy cập trước đó.
Hàm navigator.hid.requestDevice()
nhận một đối tượng bắt buộc xác định bộ lọc. Các giá trị này được dùng để so khớp mọi thiết bị được kết nối với giá trị nhận dạng nhà cung cấp USB (vendorId
), giá trị nhận dạng sản phẩm USB (productId
), giá trị trang sử dụng (usagePage
) và giá trị sử dụng (usage
). Bạn có thể lấy các giá trị này từ Kho lưu trữ mã nhận dạng USB và tài liệu về bảng sử dụng HID.
Nhiều đối tượng HIDDevice
do hàm này trả về đại diện cho nhiều 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();
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 trình duyệt mà bạn biết là đang hoạt động không đúng cách.
// 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 giá trị nhận dạng sản phẩm và nhà cung cấp USB để nhận dạng thiết bị. Thuộc tính collections
của lớp này được khởi tạo bằng nội dung mô tả phân cấp về đị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
được trả về ở trạng thái "đóng" và phải được mở bằng cách gọi open()
trước khi có thể 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ý các báo cáo đầu vào sắp tới bằng cách theo dõi các sự kiện "inputreport"
từ thiết bị. Các sự kiện đó chứa dữ liệu HID dưới dạng đối tượng DataView
(data
), thiết bị HID mà dữ liệu đó thuộc về (device
) và mã báo cáo 8 bit liên kết với báo cáo đầu vào (reportId
).
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 nào mà người dùng đã nhấn trên thiết bị Joy-Con Right để bạn có thể thử ở 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 đến thiết bị HID, hãy truyền mã báo cáo 8 bit liên kết với báo cáo đầu ra (reportId
) và các byte dưới dạng BufferSource
(data
) đến device.sendReport()
. Lời hứa được trả về sẽ giải quyết sau khi báo cáo đượ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 áp dụng cho thiết bị Joy-Con và cho bạn biết cách làm cho thiết bị này rung bằng 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 theo cả hai hướng. Các giao thức này cho phép các thiết bị và ứng dụng HID trao đổi dữ liệu HID không chuẩn hoá. Không giống như báo cáo đầu vào và đầu ra, ứng dụng không thường xuyên nhận hoặc gửi báo cáo tính năng.
Để gửi báo cáo tính năng đến thiết bị HID, hãy truyền mã báo cáo 8 bit liên kết với báo cáo tính năng (reportId
) và các byte dưới dạng BufferSource
(data
) đến device.sendFeatureReport()
. Lời hứa được trả về sẽ giải quyết sau khi báo cáo đượ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 hướng dẫn bạn cách yêu cầu thiết bị đèn nền bàn phím Apple, mở thiết bị đó và làm cho thiết bị đó nhấp nháy.
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 liên kết với báo cáo tính năng (reportId
) đến device.receiveFeatureReport()
. Lời hứa được trả về sẽ phân giải 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 thiết bị HID 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 kết nối và ngắt kết nối
Khi được cấp quyền truy cập vào 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 theo dõi các sự kiện "connect"
và "disconnect"
.
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 có thể xoá các quyền truy cập vào thiết bị HID mà trang web không còn muốn giữ lại bằng cách gọi forget()
trên thực thể HIDDevice
. Ví dụ: đối với một ứng dụng web giáo dục được dùng trên máy tính dùng chung với nhiều thiết bị, một lượng lớn quyền do người dùng tạo tích luỹ sẽ tạo ra trải nghiệm người dùng kém.
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();
Vì forget()
có trong Chrome 100 trở lên, hãy kiểm tra xem tính năng này có được hỗ trợ với các tính năng sau hay không:
if ("hid" in navigator && "forget" in HIDDevice.prototype) {
// forget() is supported.
}
Mẹo dành cho nhà phát triển
Bạn có thể dễ dàng gỡ lỗi HID trong Chrome bằng trang nội bộ about://device-log
. Tại đây, bạn có thể xem tất cả các sự kiện liên quan đến thiết bị HID và USB ở cùng một nơi.
Hãy xem trình khám phá HID để kết xuất thông tin thiết bị HID vào định dạng mà con người có thể đọc được. Tệp này ánh xạ từ các giá trị sử dụng đến tên cho từng cách sử dụng HID.
Trên hầu hết các hệ thống Linux, các thiết bị HID được liên kết với các quyền chỉ có thể đọc theo mặc định. Để cho phép Chrome mở một thiết bị HID, bạn cần thêm một quy tắc udev mới. Tạo một tệp tại /etc/udev/rules.d/50-yourdevicename.rules
có nội dung sau:
KERNEL=="hidraw*", ATTRS{idVendor}=="[yourdevicevendor]", MODE="0664", GROUP="plugdev"
Trong dòng ở trên, [yourdevicevendor]
là 057e
nếu thiết bị của bạn là Joy-Con của Nintendo Switch. Bạn cũng có thể thêm ATTRS{idProduct}
để có quy tắc cụ thể hơn. Đảm bảo user
là thành viên của nhóm plugdev
. Sau đó, bạn chỉ cần kết nối lại thiết bị.
Hỗ trợ trình duyệt
API WebHID có trên tất cả các nền tảng 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 xem thử!
Bảo mật và quyền riêng tư
Các tác giả của thông số kỹ thuật đã thiết kế và triển khai API WebHID bằng cách sử dụng các nguyên tắc cốt lõi được xác định trong bài viết Kiểm soát quyền truy cập vào các tính năng mạnh mẽ của nền tảng web, bao gồm cả quyền kiểm soát của người dùng, tính minh bạch và tính công thái học. Khả năng sử dụng API này chủ yếu được kiểm soát bởi một mô hình quyền chỉ cấp quyền truy cập vào một thiết bị HID tại một thời điểm. Để phản hồi lời nhắc của người dùng, người dùng phải chủ động thực hiện các bước để chọn một thiết bị HID cụ thể.
Để hiểu rõ về sự đánh đổi về bảo mật, hãy xem phần Những cân nhắc về bảo mật và quyền riêng tư trong thông số kỹ thuật WebHID.
Ngoài ra, Chrome sẽ kiểm tra mức sử dụng của từng bộ sưu tập cấp cao nhất và nếu một bộ sưu tập cấp cao nhất có mức sử dụng được bảo vệ (ví dụ: bàn phím, chuột chung), thì 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ị HID FIDO dùng để xác thực mạnh hơn) cũng bị chặn trong Chrome. Xem tệp danh sách chặn USB và 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ề WebHID API.
Giới thiệu cho chúng tôi về thiết kế API
API có hoạt động như mong đợi không? Hay có phương thức hoặc thuộc tính nào bị thiếu mà bạn cần để triển khai ý tưởng của mình không?
Gửi vấn đề về thông số kỹ thuật trên kho lưu trữ GitHub của API WebHID hoặc thêm ý kiến của bạn vào một vấn đề hiện có.
Báo cáo vấn đề về việc triển khai
Bạn có phát hiện lỗi khi triển khai Chrome không? Hay cách triển khai khác với thông số kỹ thuật?
Hãy xem bài viết Cách báo cáo lỗi WebHID. Hãy nhớ cung cấp càng nhiều thông tin chi tiết càng tốt, hướng dẫn đơn giản để tái tạo lỗi và đặt Components (Thành phần) thành Blink>HID
. Glitch rất phù hợp để chia sẻ các bản tái hiện nhanh chóng và dễ dàng.
Thể hiện sự ủng hộ
Bạn có định sử dụng API WebHID không? Sự ủng hộ công khai của bạn giúp nhóm Chrome ưu tiên các tính năng và cho các nhà cung cấp trình duyệt khác thấy tầm quan trọng của việc hỗ trợ các tính năng đó.
Gửi một tweet đến @ChromiumDev bằng hashtag #WebHID
và cho chúng tôi biết bạn đang sử dụng hashtag này ở đâu và như thế nào.
Đường liên kết hữu ích
- Thông số kỹ thuật
- Theo dõi lỗi
- Mục nhập trên ChromeStatus.com
- Thành phần Blink:
Blink>HID
Lời cảm ơn
Cảm ơn Matt Reynolds và Joe Medley đã xem xét bài viết này. Ảnh chụp Nintendo Switch màu đỏ và xanh dương của Sara Kurfeß và ảnh chụp máy tính xách tay màu đen và bạc của Athul Cyriac Ajay trên Unsplash.