به دستگاه های HID غیر معمول متصل شوید

رابط برنامه‌نویسی کاربردی WebHID به وب‌سایت‌ها اجازه می‌دهد تا به صفحه‌کلیدهای کمکی جایگزین و گیم‌پدهای عجیب و غریب دسترسی داشته باشند.

فرانسوا بوفور
François Beaufort

منتشر شده: ۱۵ سپتامبر ۲۰۲۰

Browser Support

  • کروم: ۸۹.
  • لبه: ۸۹.
  • فایرفاکس: پشتیبانی نمی‌شود.
  • سافاری: پشتیبانی نمی‌شود.

Source

دستگاه‌های رابط انسانی (HID) زیادی، مانند کیبوردهای جایگزین یا گیم‌پدهای عجیب و غریب، وجود دارند که برای دسترسی توسط درایورهای دستگاه سیستم‌ها، خیلی جدید، خیلی قدیمی یا خیلی غیرمعمول هستند. API WebHID با ارائه راهی برای پیاده‌سازی منطق خاص دستگاه در جاوا اسکریپت، این مشکل را حل می‌کند.

موارد استفاده پیشنهادی

یک دستگاه HID از انسان‌ها ورودی می‌گیرد یا خروجی به آن‌ها می‌دهد. نمونه‌هایی از این دستگاه‌ها شامل صفحه‌کلید، دستگاه‌های اشاره‌گر (ماوس، صفحه لمسی و غیره) و دسته‌های بازی است. پروتکل HID دسترسی به این دستگاه‌ها را در رایانه‌های رومیزی با استفاده از درایورهای سیستم عامل امکان‌پذیر می‌کند. پلتفرم وب با تکیه بر این درایورها از دستگاه‌های HID پشتیبانی می‌کند.

عدم دسترسی به دستگاه‌های HID غیرمعمول، به‌ویژه در مورد کیبوردهای کمکی جایگزین (مانند Elgato Stream Deck ، هدست‌های Jabra ، کلیدهای X ) و پشتیبانی از گیم‌پدهای عجیب و غریب، دردناک است. گیم‌پدهایی که برای دسکتاپ طراحی شده‌اند، اغلب از HID برای ورودی‌های گیم‌پد (دکمه‌ها، جوی‌استیک‌ها، تریگرها) و خروجی‌ها (LEDها، رامبل) استفاده می‌کنند.

متأسفانه، ورودی‌ها و خروجی‌های دسته بازی به خوبی استاندارد نشده‌اند و مرورگرهای وب اغلب برای دستگاه‌های خاص به منطق سفارشی نیاز دارند. این امر ناپایدار است و منجر به پشتیبانی ضعیف از دنباله طولانی دستگاه‌های قدیمی و غیرمعمول می‌شود. همچنین باعث می‌شود مرورگر به تغییرات ناگهانی در رفتار دستگاه‌های خاص وابسته باشد.

اصطلاحات

یک دستگاه رابط انسانی (HID) می‌تواند ورودی را دریافت کند یا به انسان‌ها خروجی ارائه دهد. یک پروتکل HID وجود دارد، استانداردی برای ارتباط دو طرفه بین میزبان و دستگاه که برای ساده‌سازی مراحل نصب طراحی شده است.

HID از دو مفهوم اساسی تشکیل شده است: گزارش‌ها و توصیف‌گرهای گزارش. گزارش‌ها داده‌هایی هستند که بین یک دستگاه و یک کلاینت نرم‌افزاری رد و بدل می‌شوند. توصیف‌گر گزارش، قالب و معنای داده‌هایی را که دستگاه پشتیبانی می‌کند، توصیف می‌کند.

برنامه‌ها و دستگاه‌های HID داده‌های دودویی را از طریق سه نوع گزارش تبادل می‌کنند:

نوع گزارش توضیحات
گزارش ورودی داده‌هایی که از دستگاه به برنامه ارسال می‌شود (مثلاً یک دکمه فشرده می‌شود.)
گزارش خروجی داده‌هایی که از برنامه به دستگاه ارسال می‌شود (مثلاً درخواست روشن کردن نور پس‌زمینه صفحه‌کلید.)
گزارش ویژه داده‌هایی که می‌توانند در هر دو جهت ارسال شوند. فرمت آنها به دستگاه مربوطه بستگی دارد.

یک توصیف‌گر گزارش، قالب دودویی گزارش‌های پشتیبانی‌شده توسط دستگاه را توصیف می‌کند. ساختار آن سلسله مراتبی است و می‌تواند گزارش‌ها را به عنوان مجموعه‌های مجزا در مجموعه سطح بالا گروه‌بندی کند. قالب توصیف‌گر توسط مشخصات HID تعریف می‌شود.

کاربرد HID یک مقدار عددی است که به یک ورودی یا خروجی استاندارد اشاره دارد. مقادیر کاربرد به یک دستگاه اجازه می‌دهد تا کاربرد مورد نظر دستگاه و هدف هر فیلد را در گزارش‌های خود توصیف کند. به عنوان مثال، یکی برای دکمه سمت چپ ماوس تعریف شده است. کاربردها همچنین در صفحات کاربرد سازماندهی می‌شوند که نشان‌دهنده دسته‌بندی سطح بالای دستگاه یا گزارش هستند.

از API وب‌هید (WebHID) استفاده کنید

برای بررسی اینکه آیا WebHID API پشتیبانی می‌شود، از دستور زیر استفاده کنید:

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

باز کردن یک اتصال HID

API وب‌هید (WebHID API) به صورت غیرهمزمان طراحی شده است تا از مسدود شدن رابط کاربری وب‌سایت هنگام انتظار ورودی جلوگیری کند. این مهم است زیرا داده‌های 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();
درخواست کاربر برای انتخاب یک جوی-کان نینتندو سوییچ.

همچنین می‌توانید از کلید اختیاری 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 ) و شناسه گزارش ۸ بیتی مرتبط با گزارش ورودی ( 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، شناسه گزارش ۸ بیتی مرتبط با گزارش خروجی ( reportId ) و بایت‌ها را به عنوان BufferSource ( data ) به device.sendReport() ارسال کنید. promise برگردانده شده پس از ارسال گزارش، حل می‌شود. اگر دستگاه HID از شناسه‌های گزارش استفاده نمی‌کند، reportId روی ۰ تنظیم کنید.

مثال بعدی مربوط به یک دستگاه 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 هستند که می‌توانند در هر دو جهت حرکت کنند. آن‌ها به دستگاه‌ها و برنامه‌های HID اجازه می‌دهند داده‌های HID غیر استاندارد را تبادل کنند. برخلاف گزارش‌های ورودی و خروجی، گزارش‌های ویژگی به طور منظم توسط برنامه دریافت یا ارسال نمی‌شوند.

برای ارسال یک گزارش ویژگی به یک دستگاه HID، شناسه گزارش ۸ بیتی مرتبط با گزارش ویژگی ( reportId ) و بایت‌ها را به عنوان BufferSource ( data ) به device.sendFeatureReport() ارسال کنید. promise برگردانده شده پس از ارسال گزارش، حل می‌شود. اگر دستگاه HID از شناسه‌های گزارش استفاده نمی‌کند، reportId روی ۰ تنظیم کنید.

این مثال، با نشان دادن نحوه درخواست دستگاه نور پس زمینه صفحه کلید اپل، باز کردن آن و چشمک زدن آن، استفاده از گزارش‌های ویژگی را نشان می‌دهد.

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، شناسه گزارش ۸ بیتی مرتبط با گزارش ویژگی ( reportId ) را به device.receiveFeatureReport() ارسال کنید. promise برگردانده شده با یک شیء DataView که حاوی محتویات گزارش ویژگی است، حل می‌شود. اگر دستگاه HID از شناسه‌های گزارش استفاده نمی‌کند، reportId روی ۰ تنظیم کنید.

// 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

وب‌سایت می‌تواند با فراخوانی تابع forget() در نمونه‌ی HIDDevice ، مجوزهای دسترسی به دستگاه HID که دیگر علاقه‌ای به حفظ آن ندارد را پاک کند. به عنوان مثال، برای یک برنامه‌ی وب آموزشی که در یک رایانه‌ی مشترک با دستگاه‌های زیاد استفاده می‌شود، تعداد زیادی از مجوزهای ایجاد شده توسط کاربر، تجربه‌ی کاربری ضعیفی را ایجاد می‌کند.

فراخوانی تابع forget() روی یک نمونه HIDDevice ، دسترسی به تمام رابط‌های HID روی همان دستگاه فیزیکی را لغو می‌کند.

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

از آنجایی که تابع forget() در کروم نسخه ۱۰۰ یا بالاتر موجود است، با استفاده از دستور زیر بررسی کنید که آیا این ویژگی پشتیبانی می‌شود یا خیر:

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

نکات توسعه

صفحه داخلی برای اشکال‌زدایی HID.

اشکال‌زدایی HID در کروم با استفاده از صفحه داخلی about://device-log است که در آن می‌توانید تمام رویدادهای مربوط به HID و دستگاه USB را در یک مکان واحد مشاهده کنید.

برای نمایش اطلاعات دستگاه HID در قالبی قابل خواندن توسط انسان، HID explorer را بررسی کنید. این ابزار، مقادیر استفاده را به نام‌های هر HID نگاشت می‌کند.

در اکثر سیستم‌های لینوکس، دستگاه‌های HID به طور پیش‌فرض با مجوزهای فقط خواندنی نگاشت می‌شوند. برای اینکه به کروم اجازه دهید یک دستگاه 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 فهرست شده‌اند.

امنیت و حریم خصوصی

نویسندگان این مشخصات، API وب‌هید (WebHID) را با استفاده از اصول اصلی تعریف‌شده در «کنترل دسترسی به ویژگی‌های قدرتمند پلتفرم وب» ، از جمله کنترل کاربر، شفافیت و ارگونومی، طراحی و پیاده‌سازی کرده‌اند. قابلیت استفاده از این API در درجه اول توسط یک مدل مجوز محدود شده است که دسترسی به فقط یک دستگاه HID را در یک زمان مجاز می‌کند. در پاسخ به درخواست کاربر، کاربر باید اقدامات فعالی را برای انتخاب یک دستگاه HID خاص انجام دهد.

برای درک بهتر بده‌بستان‌های امنیتی، بخش ملاحظات امنیتی و حریم خصوصی در مشخصات WebHID را بررسی کنید.

علاوه بر این، کروم میزان استفاده از هر مجموعه سطح بالا را بررسی می‌کند و اگر یک مجموعه سطح بالا دارای کاربرد محافظت‌شده (مثلاً صفحه‌کلید عمومی، ماوس) باشد، وب‌سایت قادر به ارسال و دریافت هیچ گزارشی که در آن مجموعه تعریف شده باشد، نخواهد بود. لیست کامل کاربردهای محافظت‌شده به صورت عمومی در دسترس است.

توجه داشته باشید که دستگاه‌های HID حساس به امنیت (مانند دستگاه‌های FIDO HID که برای احراز هویت قوی‌تر استفاده می‌شوند) نیز در کروم مسدود شده‌اند. به فایل‌های لیست مسدودیت USB و لیست مسدودیت HID مراجعه کنید.

بازخورد

تیم کروم دوست دارد نظرات و تجربیات شما را در مورد WebHID API بشنود.

در مورد طراحی API به ما بگویید

آیا چیزی در مورد API وجود دارد که آنطور که انتظار می‌رود کار نمی‌کند؟ یا متدها یا ویژگی‌هایی که برای پیاده‌سازی ایده خود به آنها نیاز دارید، وجود ندارند؟

یک مشکل خاص را در مخزن WebHID API GitHub ثبت کنید یا نظرات خود را به یک مشکل موجود اضافه کنید.

گزارش مشکل در پیاده‌سازی

آیا در پیاده‌سازی کروم اشکالی پیدا کردید؟ یا پیاده‌سازی با مشخصات متفاوت است؟

نحوه ثبت اشکالات WebHID را بررسی کنید. حتماً تا حد امکان جزئیات را ذکر کنید، دستورالعمل‌هایی برای تولید مجدد اشکال ارائه دهید و Components را روی Blink>HID تنظیم کنید.

لینک‌های مفید

تقدیرنامه‌ها

با تشکر از مت رینولدز و جو مدلی برای نقدهایشان.