เชื่อมต่อกับอุปกรณ์ HID ที่ผิดปกติ

WebHID API ช่วยให้เว็บไซต์เข้าถึงคีย์บอร์ดเสริมและเกมแพดที่แปลกใหม่ได้

François Beaufort
François Beaufort

เผยแพร่: 15 กันยายน 2020

Browser Support

  • Chrome: 89.
  • Edge: 89.
  • Firefox: not supported.
  • Safari: not supported.

Source

มีอุปกรณ์ที่โต้ตอบกับมนุษย์โดยตรง (HID) มากมาย เช่น แป้นพิมพ์ทางเลือกหรือเกมแพดที่แปลกใหม่ ซึ่งใหม่เกินไป เก่าเกินไป หรือไม่เป็นที่นิยมเกินไปจนไดรเวอร์อุปกรณ์ของระบบเข้าถึงไม่ได้ WebHID API แก้ปัญหานี้ด้วยการ ให้วิธีใช้ตรรกะเฉพาะอุปกรณ์ใน JavaScript

กรณีการใช้งานที่แนะนำ

อุปกรณ์ HID รับอินพุตจากหรือแสดงเอาต์พุตต่อมนุษย์ ตัวอย่างอุปกรณ์ ได้แก่ แป้นพิมพ์ อุปกรณ์ชี้ (เมาส์ หน้าจอสัมผัส ฯลฯ) และเกมแพด โปรโตคอล HID ช่วยให้เข้าถึงอุปกรณ์เหล่านี้ในคอมพิวเตอร์เดสก์ท็อป ได้โดยใช้ไดรเวอร์ระบบปฏิบัติการ แพลตฟอร์มเว็บรองรับอุปกรณ์ HID โดยใช้ไดรเวอร์เหล่านี้

การเข้าถึงอุปกรณ์ HID ที่ไม่ค่อยพบได้ทั่วไปไม่ได้เป็นปัญหามากนักเมื่อพูดถึงแป้นพิมพ์เสริมทางเลือก (เช่น Elgato Stream Deck, ชุดหูฟัง Jabra, X-keys) และการรองรับเกมแพดที่แปลกใหม่ เกมแพดที่ออกแบบมาสำหรับเดสก์ท็อป มักใช้ HID สำหรับอินพุต (ปุ่ม จอยสติ๊ก ทริกเกอร์) และเอาต์พุต (ไฟ LED การสั่น) ของเกมแพด

อย่างไรก็ตาม อินพุตและเอาต์พุตของเกมแพดไม่ได้ เป็นมาตรฐานที่ดี และเว็บเบราว์เซอร์มักต้องใช้ตรรกะที่กำหนดเองสำหรับอุปกรณ์บางอย่าง ซึ่งเป็นสิ่งที่ยั่งยืนไม่ได้และส่งผลให้การสนับสนุนอุปกรณ์รุ่นเก่าและ อุปกรณ์ที่ไม่ค่อยพบนั้นไม่ดี นอกจากนี้ยังทำให้เบราว์เซอร์ต้องอาศัยข้อบกพร่องในลักษณะการทำงาน ของอุปกรณ์บางรุ่นด้วย

คำศัพท์

อุปกรณ์ที่โต้ตอบกับมนุษย์โดยตรง (HID) สามารถรับอินพุตหรือแสดงเอาต์พุตต่อมนุษย์ได้ มีโปรโตคอล HID ซึ่งเป็นมาตรฐานสำหรับการสื่อสารแบบ 2 ทางระหว่างโฮสต์กับอุปกรณ์ที่ออกแบบมาเพื่อลดความซับซ้อนของขั้นตอนการติดตั้ง

HID ประกอบด้วยแนวคิดพื้นฐาน 2 อย่าง ได้แก่ รายงานและตัวอธิบายรายงาน รายงานคือข้อมูลที่แลกเปลี่ยนระหว่างอุปกรณ์กับไคลเอ็นต์ซอฟต์แวร์ ตัวอธิบายรายงานจะอธิบายรูปแบบและความหมายของข้อมูลที่อุปกรณ์รองรับ

แอปพลิเคชันและอุปกรณ์ HID แลกเปลี่ยนข้อมูลไบนารีผ่านรายงาน 3 ประเภท ได้แก่

ประเภทรายงาน คำอธิบาย
รายงานอินพุต ข้อมูลที่ส่งจากอุปกรณ์ไปยังแอปพลิเคชัน (เช่น มีการกดปุ่ม)
รายงานเอาต์พุต ข้อมูลที่ส่งจากแอปพลิเคชันไปยังอุปกรณ์ (เช่น คำขอเปิดไฟแบ็กไลต์ของคีย์บอร์ด)
รายงานฟีเจอร์ ข้อมูลที่อาจส่งได้ทั้ง 2 ทาง รูปแบบจะขึ้นอยู่กับอุปกรณ์

ตัวอธิบายรายงานจะอธิบายรูปแบบไบนารีของรายงานที่อุปกรณ์รองรับ โครงสร้างของรายงานเป็นแบบลำดับชั้นและสามารถจัดกลุ่มรายงานไว้ด้วยกันเป็นคอลเล็กชันที่แตกต่างกัน ภายในคอลเล็กชันระดับบนสุด รูปแบบของตัวอธิบายมีการกำหนดโดยข้อกำหนด HID

การใช้งาน HID คือค่าตัวเลขที่อ้างอิงถึงอินพุตหรือเอาต์พุตมาตรฐาน ค่าการใช้งานช่วยให้อุปกรณ์อธิบายการใช้งานที่ต้องการของอุปกรณ์และ วัตถุประสงค์ของแต่ละฟิลด์ในรายงานได้ เช่น กำหนดปุ่มซ้ายของเมาส์ นอกจากนี้ ระบบยังจัดระเบียบการใช้งานเป็นหน้าการใช้งาน ซึ่งจะระบุหมวดหมู่ระดับสูงของอุปกรณ์หรือรายงาน

ใช้ WebHID API

หากต้องการตรวจสอบว่าระบบรองรับ WebHID API หรือไม่ ให้ใช้

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

เปิดการเชื่อมต่อ HID

WebHID API ได้รับการออกแบบมาให้ทำงานแบบไม่พร้อมกันเพื่อป้องกันไม่ให้ UI ของเว็บไซต์ถูกบล็อกเมื่อรอรับอินพุต ซึ่งถือเป็นสิ่งสำคัญเนื่องจากสามารถรับข้อมูล HID ได้ทุกเมื่อ จึงต้องมีวิธีรับฟังข้อมูลดังกล่าว

หากต้องการเปิดการเชื่อมต่อ HID ให้เข้าถึงออบเจ็กต์ HIDDevice ก่อน โดยคุณสามารถ แจ้งให้ผู้ใช้เลือกอุปกรณ์โดยการเรียกใช้ navigator.hid.requestDevice() หรือเลือกอุปกรณ์จาก navigator.hid.getDevices() ซึ่งจะแสดงรายการอุปกรณ์ที่เว็บไซต์ได้รับสิทธิ์เข้าถึง ก่อนหน้านี้

ฟังก์ชัน navigator.hid.requestDevice() ต้องใช้ออบเจ็กต์ที่จำเป็นซึ่ง กำหนดตัวกรอง ซึ่งใช้เพื่อจับคู่อุปกรณ์ที่เชื่อมต่อกับตัวระบุผู้ให้บริการ USB (vendorId), ตัวระบุผลิตภัณฑ์ USB (productId), ค่าหน้าการใช้งาน (usagePage) และค่าการใช้งาน (usage) คุณสามารถดูค่าดังกล่าวได้จากที่เก็บรหัส USB และเอกสารตารางการใช้งาน HID

ออบเจ็กต์ HIDDevice หลายรายการที่ฟังก์ชันนี้แสดงผลแสดงถึงอินเทอร์เฟซ HID หลายรายการในอุปกรณ์จริงเดียวกัน

// 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();
พรอมต์ของผู้ใช้สำหรับการเลือก Nintendo Switch Joy-Con

นอกจากนี้ คุณยังใช้คีย์ exclusionFilters ที่ไม่บังคับใน navigator.hid.requestDevice() เพื่อยกเว้นอุปกรณ์บางเครื่องจากตัวเลือกเบราว์เซอร์ ที่ทราบว่าทำงานผิดปกติได้ด้วย

// 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 }],
});

ออบเจ็กต์ HIDDevice มีตัวระบุผู้ให้บริการและผลิตภัณฑ์ USB สำหรับการระบุอุปกรณ์ collections ของอุปกรณ์จะเริ่มต้นด้วยคำอธิบายแบบลำดับชั้น ของรูปแบบรายงานของอุปกรณ์

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
}

โดยค่าเริ่มต้น ระบบจะส่งคืนอุปกรณ์ HIDDevice ในสถานะ "ปิด" และต้องเปิดโดยการโทรหา open() ก่อนจึงจะส่งหรือรับข้อมูลได้

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

รับรายงานอินพุต

Nintendo Switch Joy-Cons

เมื่อสร้างการเชื่อมต่อ HID แล้ว คุณจะจัดการรายงานอินพุตที่เข้ามาได้โดยการฟังเหตุการณ์ "inputreport" จากอุปกรณ์ เหตุการณ์เหล่านั้น มีข้อมูล HID เป็นออบเจ็กต์ DataView (data) อุปกรณ์ HID ที่เป็นของออบเจ็กต์นั้น (device) และรหัสรายงาน 8 บิตที่เชื่อมโยงกับรายงานอินพุต (reportId)

จากตัวอย่างก่อนหน้า โค้ดนี้จะช่วยให้คุณตรวจหาปุ่มที่ผู้ใช้กดบนอุปกรณ์ Joy-Con Right เพื่อให้คุณลองใช้ที่บ้านได้

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]}.`);
});

ดูเดโมใน CodePen

ส่งรายงานเอาต์พุต

หากต้องการส่งรายงานเอาต์พุตไปยังอุปกรณ์ HID ให้ส่งรหัสรายงาน 8 บิตที่เชื่อมโยง กับรายงานเอาต์พุต (reportId) และไบต์เป็น BufferSource (data) ไปยัง device.sendReport() Promise ที่ส่งคืนจะได้รับการแก้ไขเมื่อส่งรายงานแล้ว หากอุปกรณ์ HID ไม่ใช้รหัสรายงาน ให้ตั้งค่า reportId เป็น 0

ตัวอย่างถัดไปใช้กับอุปกรณ์ Joy-Con และแสดงวิธีทำให้อุปกรณ์สั่นด้วยรายงานเอาต์พุต

// 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.
const rumbleData = [ /* ... */ ];
await device.sendReport(0x10, new Uint8Array(rumbleData));

ดูเดโมใน CodePen

ส่งและรับรายงานฟีเจอร์

รายงานฟีเจอร์เป็นรายงานข้อมูล HID ประเภทเดียวที่สามารถเดินทางได้ทั้ง 2 ทิศทาง ซึ่งช่วยให้อุปกรณ์และแอปพลิเคชัน HID แลกเปลี่ยนข้อมูล HID ที่ไม่ได้มาตรฐานได้ รายงานฟีเจอร์จะไม่เหมือนกับรายงานอินพุตและเอาต์พุตตรงที่แอปพลิเคชันจะไม่ได้รับหรือส่งรายงานเป็นประจำ

หากต้องการส่งรายงานฟีเจอร์ไปยังอุปกรณ์ HID ให้ส่งรหัสรายงาน 8 บิตที่เชื่อมโยง กับรายงานฟีเจอร์ (reportId) และไบต์เป็น BufferSource (data) ไปยัง device.sendFeatureReport() Promise ที่ส่งคืนจะได้รับการแก้ไขเมื่อส่งรายงานแล้ว หากอุปกรณ์ HID ไม่ใช้รหัสรายงาน ให้ตั้งค่า reportId เป็น 0

ตัวอย่างนี้แสดงการใช้รายงานฟีเจอร์โดยแสดงวิธี ขออุปกรณ์ไฟแบ็กไลท์ของแป้นพิมพ์ Apple เปิดอุปกรณ์ และทำให้อุปกรณ์กะพริบ

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

ดูเดโมใน CodePen

หากต้องการรับรายงานฟีเจอร์จากอุปกรณ์ HID ให้ส่งรหัสรายงาน 8 บิต ที่เชื่อมโยงกับรายงานฟีเจอร์ (reportId) ไปยัง device.receiveFeatureReport() Promise ที่แสดงผลจะได้รับการแก้ไขด้วยออบเจ็กต์ DataView ซึ่งมีเนื้อหาของรายงานฟีเจอร์ หากอุปกรณ์ HID ไม่ได้ใช้รหัสรายงาน ให้ตั้งค่า reportId เป็น 0

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

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

ฟังการเชื่อมต่อและการยกเลิกการเชื่อมต่อ

เมื่อได้รับสิทธิ์เข้าถึงอุปกรณ์ HID แล้ว เว็บไซต์จะ รับเหตุการณ์การเชื่อมต่อและการยกเลิกการเชื่อมต่อได้อย่างต่อเนื่องโดยการฟังเหตุการณ์ "connect" และ "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.
});

เพิกถอนสิทธิ์เข้าถึงอุปกรณ์ HID

เว็บไซต์สามารถล้างสิทธิ์เข้าถึงอุปกรณ์ HID ที่ไม่ต้องการเก็บรักษาอีกต่อไปได้โดยเรียกใช้ forget() ในอินสแตนซ์ HIDDevice ตัวอย่างเช่น สำหรับเว็บแอปพลิเคชันเพื่อการศึกษาที่ใช้ในคอมพิวเตอร์ที่ใช้ร่วมกันกับอุปกรณ์จำนวนมาก สิทธิ์ที่ผู้ใช้สร้างขึ้นจำนวนมากที่สะสมไว้จะทำให้ประสบการณ์ของผู้ใช้ไม่ดี

การเรียกใช้ forget() ในอินสแตนซ์ HIDDevice เดียวจะเป็นการเพิกถอนสิทธิ์เข้าถึงอินเทอร์เฟซ HID ทั้งหมดในอุปกรณ์จริงเดียวกัน

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

เนื่องจาก forget() พร้อมใช้งานใน Chrome 100 ขึ้นไป ให้ตรวจสอบว่าฟีเจอร์นี้ รองรับการใช้งานกับรายการต่อไปนี้หรือไม่

if ("hid" in navigator && "forget" in HIDDevice.prototype) {
  // forget() is supported.
}

เคล็ดลับสำหรับนักพัฒนาซอฟต์แวร์

หน้าเว็บภายในสำหรับแก้ไขข้อบกพร่องของ HID

แก้ไขข้อบกพร่องของ HID ใน Chrome ด้วยหน้าภายใน about://device-log ซึ่งคุณจะดูเหตุการณ์ที่เกี่ยวข้องกับอุปกรณ์ HID และ USB ทั้งหมดได้ในที่เดียว

ดูเครื่องมือสำรวจ HID เพื่อส่งข้อมูลอุปกรณ์ HID ไปยังรูปแบบที่มนุษย์อ่านได้ โดยจะแมปจากค่าการใช้งานไปยังชื่อสำหรับการใช้งาน HID แต่ละรายการ

ในระบบ Linux ส่วนใหญ่ อุปกรณ์ HID จะได้รับการแมปด้วยสิทธิ์แบบอ่านอย่างเดียวโดย ค่าเริ่มต้น หากต้องการอนุญาตให้ Chrome เปิดอุปกรณ์ HID คุณจะต้องเพิ่มกฎ udev ใหม่ สร้างไฟล์ที่ /etc/udev/rules.d/50-yourdevicename.rules โดยมีเนื้อหาต่อไปนี้

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

ในโค้ดนี้ [yourdevicevendor] คือ 057e เช่น หากอุปกรณ์ของคุณคือ Nintendo Switch Joy-Con ATTRS{idProduct} สามารถเพิ่มเพื่อสร้างกฎที่เฉพาะเจาะจงมากขึ้นได้ ตรวจสอบว่า user เป็นสมาชิกของกลุ่ม plugdev จากนั้นเพียง เชื่อมต่ออุปกรณ์อีกครั้ง

การสาธิต

ตัวอย่าง WebHID บางส่วนแสดงอยู่ที่ web.dev/hid-examples

ความปลอดภัยและความเป็นส่วนตัว

ผู้เขียนข้อกำหนดได้ออกแบบและใช้ WebHID API โดยใช้หลักการหลักที่กำหนดไว้ในการควบคุมการเข้าถึงฟีเจอร์ที่มีประสิทธิภาพของแพลตฟอร์มเว็บ ซึ่งรวมถึงการควบคุมของผู้ใช้ ความโปร่งใส และการยศาสตร์ ความสามารถในการใช้ API นี้ขึ้นอยู่กับโมเดลสิทธิ์ที่ให้สิทธิ์เข้าถึงอุปกรณ์ HID เพียงเครื่องเดียว ในแต่ละครั้งเป็นหลัก เมื่อได้รับข้อความแจ้งจากผู้ใช้ ผู้ใช้ต้องดำเนินการ เพื่อเลือกอุปกรณ์ HID ที่ต้องการ

หากต้องการทำความเข้าใจข้อแลกเปลี่ยนด้านความปลอดภัย โปรดดูส่วนข้อควรพิจารณาด้านความปลอดภัยและความเป็นส่วนตัวของข้อกำหนด WebHID

นอกจากนี้ Chrome ยังตรวจสอบการใช้งานคอลเล็กชันระดับบนสุดแต่ละรายการ และหากคอลเล็กชันระดับบนสุดมีการใช้งานที่ได้รับการปกป้อง (เช่น คีย์บอร์ด เมาส์ทั่วไป) เว็บไซต์จะไม่สามารถส่งและรับรายงานใดๆ ที่กำหนดไว้ในคอลเล็กชันนั้น รายการการใช้งานที่ได้รับการปกป้องทั้งหมดพร้อมให้บริการแก่สาธารณะ

โปรดทราบว่าอุปกรณ์ HID ที่มีความละเอียดอ่อนด้านความปลอดภัย (เช่น อุปกรณ์ FIDO HID ที่ใช้สำหรับการ ตรวจสอบสิทธิ์ที่รัดกุมยิ่งขึ้น) จะถูกบล็อกใน Chrome ด้วย ดูไฟล์รายการที่บล็อก USB และ รายการที่บล็อก HID

ความคิดเห็น

ทีม Chrome อยากทราบความคิดเห็นและประสบการณ์ของคุณเกี่ยวกับ WebHID API

บอกเราเกี่ยวกับการออกแบบ API

มีอะไรเกี่ยวกับ API ที่ไม่ทำงานตามที่คาดไว้ไหม หรือมีเมธอดหรือพร็อพเพอร์ตี้ที่ขาดหายไปซึ่งคุณต้องใช้เพื่อนำไอเดียไปใช้ไหม

แจ้งปัญหาเกี่ยวกับข้อกำหนดในที่เก็บ GitHub ของ WebHID API หรือแสดงความคิดเห็น ในปัญหาที่มีอยู่

รายงานปัญหาเกี่ยวกับการติดตั้งใช้งาน

หากพบข้อบกพร่องในการใช้งาน Chrome หรือการติดตั้งใช้งาน แตกต่างจากข้อกำหนด

ดูวิธีรายงานข้อบกพร่องของ WebHID โปรดใส่รายละเอียดให้มากที่สุดเท่าที่จะเป็นไปได้ ระบุวิธีการจำลองข้อบกพร่อง และตั้งค่าคอมโพเนนต์เป็น Blink>HID

ลิงก์ที่มีประโยชน์

คำขอบคุณ

ขอขอบคุณMatt Reynolds และ Joe Medley สำหรับรีวิว