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

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

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

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

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

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

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

اصطلاحات

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

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

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

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

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

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

با استفاده از WebHID API

تشخیص ویژگی

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

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

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

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();
اسکرین شات درخواست دستگاه HID در یک وب سایت.
درخواست کاربر برای انتخاب 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();

دریافت گزارش ورودی

هنگامی که اتصال HID برقرار شد، می توانید با گوش دادن به رویدادهای "inputreport" از دستگاه، گزارش های ورودی دریافتی را مدیریت کنید. این رویدادها شامل داده‌های HID به عنوان یک شی DataView ( data )، دستگاه HID متعلق به آن ( device ) و شناسه گزارش 8 بیتی مرتبط با گزارش ورودی ( reportId ) است.

عکس سوئیچ نینتندو قرمز و آبی.
دستگاه های Nintendo Switch Joy-Con.

در ادامه مثال قبلی، کد زیر به شما نشان می‌دهد که چگونه تشخیص دهید کاربر کدام دکمه را روی دستگاه 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]}.`);
});

ارسال گزارش خروجی

برای ارسال یک گزارش خروجی به یک دستگاه HID، شناسه گزارش 8 بیتی مرتبط با گزارش خروجی ( reportId ) و بایت ها را به عنوان BufferSource ( data ) به device.sendReport() ارسال کنید. قول برگشتی پس از ارسال گزارش برطرف می شود. اگر دستگاه 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 below.
const rumbleData = [ /* ... */ ];
await device.sendReport(0x10, new Uint8Array(rumbleData));

ارسال و دریافت گزارش ویژگی

گزارش های ویژگی تنها نوع گزارش داده های HID هستند که می توانند در هر دو جهت حرکت کنند. آنها به دستگاه ها و برنامه های HID اجازه می دهند تا داده های HID غیر استاندارد را مبادله کنند. برخلاف گزارش های ورودی و خروجی، گزارش های ویژگی به طور منظم توسط برنامه دریافت یا ارسال نمی شود.

عکس سیاه و نقره ای لپ تاپ کامپیوتر.
صفحه کلید لپ تاپ

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

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

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

برای دریافت گزارش ویژگی از یک دستگاه HID، شناسه گزارش 8 بیتی مرتبط با گزارش ویژگی ( reportId ) را به device.receiveFeatureReport() ارسال کنید. وعده بازگشتی با یک شی 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 در کروم با صفحه داخلی، about://device-log که در آن می توانید همه رویدادهای مربوط به دستگاه HID و USB را در یک مکان مشاهده کنید، آسان است.

اسکرین شات از صفحه داخلی برای رفع اشکال HID.
صفحه داخلی در کروم برای رفع اشکال HID.

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

در اکثر سیستم های لینوکس، دستگاه های 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 API در تمام پلتفرم‌های دسکتاپ (ChromeOS، Linux، macOS و Windows) در Chrome 89 در دسترس است.

دموها

برخی از نمایش‌های WebHID در web.dev/hid-examples فهرست شده‌اند. برو نگاه کن

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

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

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

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

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

بازخورد

تیم Chrome مایل است درباره افکار و تجربیات شما درباره WebHID API بشنوند.

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

آیا چیزی در مورد API وجود دارد که مطابق انتظار کار نمی کند؟ یا آیا روش ها یا ویژگی هایی وجود دارد که برای اجرای ایده خود به آنها نیاز دارید؟

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

گزارش مشکل در اجرا

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

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

نشان دادن پشتیبانی

آیا قصد دارید از WebHID API استفاده کنید؟ پشتیبانی عمومی شما به تیم Chrome کمک می‌کند ویژگی‌ها را اولویت‌بندی کند و به سایر فروشندگان مرورگر نشان می‌دهد که چقدر حمایت از آنها ضروری است.

با استفاده از هشتگ #WebHID یک توییت به ChromiumDev@ ارسال کنید و به ما اطلاع دهید که کجا و چگونه از آن استفاده می‌کنید.

لینک های مفید

قدردانی ها

با تشکر از مت رینولدز و جو مدلی برای بررسی این مقاله. عکس قرمز و آبی نینتندو سوییچ توسط سارا کورفیس و عکس سیاه و نقره‌ای لپ‌تاپ توسط Athul Cyriac Ajay در Unsplash.