שיחה עם הבקר של Stadia עם WebHID

בקר Stadia שמוצג כאן פועל כמו בקר משחקים רגיל, כלומר לא לכל הלחצנים שלו יש גישה באמצעות Gamepad API. עם WebHID, עכשיו יש לכם גישה לכפתורים החסרים.

מאז ש-Stadia נסגר, רבים חששו שהבקר יהפוך לחתיכת חומרה חסרת תועלת במזבלה. למזלכם, צוות Stadia החליט לפתוח את השלט הרחוק ל-Stadia במקום זאת, על ידי מתן קושחה מותאמת אישית שתוכלו להפעיל בשלט הרחוק שלכם על ידי מעבר לדף מצב Bluetooth של Stadia. כך השלט הרחוק ל-Stadia יופיע כג'ויסטיק רגיל שאפשר לחבר באמצעות כבל USB או באופן אלחוטי באמצעות Bluetooth. דף ה-Bluetooth של Stadia מוצג בגאווה בדף הראווה של Project Fugu API, והוא עצמו משתמש ב-WebHID וב-WebUSB, אבל זה לא הנושא של המאמר הזה. בפוסט הזה אני רוצה להסביר איך אפשר לדבר עם השלט הרחוק ל-Stadia באמצעות WebHID.

השלט הרחוק ל-Stadia כגיימפאד רגיל

אחרי ההפעלה, הבקר מופיע במערכת ההפעלה כבקר משחקים רגיל. בצילום המסך הבא אפשר לראות את הפריסה הנפוצה של הכפתורים והצירים בבקר משחקים רגיל. כפי שמוגדר במפרט של Gamepad API, לבקרי משחקים רגילים יש לחצנים מ-0 עד 16, כלומר 17 בסך הכול (הלחצנים של כיווני התנועה נחשבים לארבעה לחצנים). אם תנסו את שלט Stadia בהדמו של בודק הגיימפדים, תראו שהוא עובד בצורה חלקה.

סכימה של גיימפד רגיל עם הצירים והלחצנים השונים שמסומנים.

עם זאת, אם סופרים את הלחצנים בשלט הרחוק ל-Stadia, יש 19. אם תנסו את כל הלחצנים אחד אחרי השני בבודק של בקר המשחקים, תגלו שהלחצנים Assistant ו-Capture לא פועלים. גם אם buttonsמאפיין בקר המשחקים כפי שמוגדר במפרט בקר המשחקים הוא פתוח, מכיוון שבקר Stadia מופיע כבקר משחקים רגיל, רק כפתורים 0 עד 16 ממופים. עדיין אפשר להשתמש בלחצנים האחרים, אבל ברוב המשחקים לא מצפים שהם יהיו קיימים.

WebHID לעזרה

הודות ל-WebHID API, אפשר לדבר עם הלחצנים החסרים 17 ו-18. אם רוצים, אפשר גם לקבל נתונים על כל הלחצנים והצירים האחרים שכבר זמינים דרך Gamepad API. השלב הראשון הוא לגלות איך השלט של Stadia מדווח על עצמו למערכת ההפעלה. אחת הדרכים לעשות זאת היא לפתוח את מסוף כלי הפיתוח ל-Chrome בכל דף אקראי, ולבקש רשימה לא מסוננת של מכשירים מ-WebHID API. לאחר מכן בוחרים ידנית את השלט הרחוק ל-Stadia לבדיקה נוספת. כדי לקבל רשימה לא מסוננת של מכשירים, פשוט מעבירים מערך ריק של filters options.

const [device] = await navigator.hid.requestDevice({filters: []});

בכלי לבחירת מכשירים, האפשרות הלפני האחרונה נראית כמו שלט Stadia.

כלי לבחירת מכשירים ב-WebHID API שמציג כמה מכשירים לא קשורים, ושלט Stadia במקום הלפני אחרון.

אחרי שבוחרים במכשיר Stadia Controller rev. A, מתעדים את אובייקט HIDDevice שמתקבל במסוף. יוצגו productId (37888, שהוא 0x9400 בפורמט הקסדצימלי) וvendorId (6353, שהוא 0x18d1 בפורמט הקסדצימלי) של השלט הרחוק ל-Stadia. אם מחפשים את vendorID בטבלה הרשמית של מזהי ספקי USB, מגלים ש-6353 ממופה למה שציפיתם: Google Inc..

מסוף כלי הפיתוח של Chrome שמציג את הפלט של רישום האובייקט HIDDevice.

אפשרות נוספת היא לעבור אל chrome://device-log/ בסרגל כתובות ה-URL, ללחוץ על הלחצן ניקוי, לחבר את בקר Stadia ואז ללחוץ על רענון. כך תוכלו לקבל את אותו המידע.

ממשק הניפוי באגים chrome://device-log שמציג מידע על שלט Stadia שמחובר למחשב.

אפשרות נוספת היא להשתמש בכלי HID Explorer, שמאפשר לכם לבדוק פרטים נוספים על מכשירי ה-HID שמחוברים למחשב.

משתמשים בשני המזהים האלה, vendorId ו-productId, כדי לשפר את מה שמוצג בכלי לבחירת מכשירים. לשם כך, מסננים בצורה נכונה את המכשיר הנכון של WebHID.

const [stadiaController] = await navigator.hid.requestDevice({filters: [{
  vendorId: 6353,
  productId: 37888,
}]});

עכשיו הרעש מכל המכשירים שלא קשורים נעלם, ומוצג רק השלט של Stadia.

כלי לבחירת מכשיר ב-WebHID API שמציג רק את השלט הרחוק ל-Stadia.

לאחר מכן, פותחים את HIDDevice על ידי קריאה ל-method‏ open().

await stadiaController.open();

מבצעים שוב את הרישום של HIDDevice, והדגל opened מוגדר לערך true.

מסוף כלי הפיתוח של Chrome שמציג את הפלט של רישום האובייקט HIDDevice ביומן אחרי הפתיחה שלו.

כשהמכשיר פתוח, מקשיבים לאירועים נכנסים של inputreport על ידי צירוף של event listener.

stadiaController.addEventListener('inputreport', (e) => {
  console.log(e);
});

כשלוחצים על לחצן Assistant בשלט הרחוק ומרפים, שני אירועים נרשמים ביומן במסוף. אפשר לחשוב עליהם כעל אירועים של "לחיצה על לחצן Assistant" ו "שחרור לחצן Assistant". במבט ראשון, קשה להבחין בין שני האירועים, מלבד timeStamp.

מסוף כלי הפיתוח ל-Chrome שמציג אובייקטים של HIDInputReportEvent שנרשמים ביומן.

המאפיין reportId של הממשק HIDInputReportEvent מחזיר את קידומת הזיהוי של בייט אחד עבור הדוח הזה, או 0 אם ממשק ה-HID לא משתמש במזהי דוחות. במקרה הזה, 3. הסוד נמצא במאפיין data, שמיוצג כ-DataView בגודל 10. ‫DataView מספק ממשק ברמה נמוכה לקריאה ולכתיבה של כמה סוגי מספרים בפורמט בינארי ArrayBuffer. כדי לקבל משהו קל יותר לעיכול מהייצוג הזה, צריך ליצור Uint8Array מתוך ArrayBuffer, כדי שתוכלו לראות את המספרים השלמים החיוביים של 8 ביט.

const data = new Uint8Array(event.data.buffer);

כשמתעדים שוב את נתוני האירועים של דוח הקלט, הדברים מתחילים להיות ברורים יותר, ואירועי 'לחיצה על לחצן Assistant' ו'שחרור לחצן Assistant' מתחילים להיות מובנים. נראה שהמספר השלם הראשון (8 בשני האירועים) קשור ללחיצות על לחצנים, והמספר השלם השני (2 ו-0) קשור לשאלה אם לחצו על לחצן Assistant או לא.

מסוף כלי הפיתוח ל-Chrome שמוצגים בו אובייקטים מסוג Uint8Array שנרשמים ביומן לכל HIDInputReportEvent.

אם לוחצים על הלחצן צילום במקום על הלחצן Assistant, אפשר לראות שהמספר השלם השני משתנה מ-1 כשלוחצים על הלחצן ל-0 כשמשחררים אותו. כך תוכלו לכתוב 'דרייבר' פשוט מאוד שיאפשר לכם להשתמש בשני הלחצנים החסרים.

stadia.addEventListener('inputreport', (event) => {
  if (!e.reportId === 3) {
    return;
  }
  const data = new Uint8Array(event.data.buffer);
  if (data[0] === 8) {
    if (data[1] === 1) {
      hidButtons[1].classList.add('highlight');
    } else if (data[1] === 2) {
      hidButtons[0].classList.add('highlight');
    } else if (data[1] === 3) {
      hidButtons[0].classList.add('highlight');
      hidButtons[1].classList.add('highlight');
    } else {
      hidButtons[0].classList.remove('highlight');
      hidButtons[1].classList.remove('highlight');
    }
  }
});

באמצעות גישה של הנדסה הפוכה כמו זו, תוכלו להבין, כפתור אחרי כפתור וציר אחרי ציר, איך לתקשר עם בקר Stadia באמצעות WebHID. אחרי שמבינים את העיקרון, כל השאר הוא עבודת מיפוי של מספרים שלמים כמעט באופן מכני.

מה שחסר עכשיו הוא חוויית החיבור החלקה ש-Gamepad API מספק. מטעמי אבטחה, תמיד צריך לעבור את תהליך הבחירה הראשוני פעם אחת כדי לעבוד עם מכשיר WebHID כמו בקר Stadia, אבל בחיבורים עתידיים אפשר להתחבר מחדש למכשירים מוכרים. כדי לעשות זאת, מפעילים את method‏ getDevices().

let stadiaController;
const [device] = await navigator.hid.getDevices();
if (device && device.vendorId === 6353 && device.productId === 37888) {
  stadiaController = device;
}

הדגמה (דמו)

אתם יכולים לראות את בקר Stadia שנשלט במשותף על ידי Gamepad API ו-WebHID API בהדגמה שיצרתי. מומלץ לעיין בקוד המקור, שמבוסס על קטעי הקוד מהמאמר הזה. לצורך פשטות, מוצגים רק הלחצנים A,‏ B,‏ X ו-Y (שנשלטים על ידי Gamepad API), והלחצנים Assistant ו-Capture (שנשלטים על ידי WebHID API). מתחת לתמונה של השלט, אפשר לראות את נתוני WebHID הגולמיים, כדי לקבל תחושה של כל הכפתורים והצירים בשלט.

אפליקציית ההדגמה של בקר Stadia שבה מוצגים הלחצנים A,‏ B,‏ X ו-Y שנשלטים על ידי Gamepad API, והלחצנים Assistant ו-Capture שנשלטים על ידי WebHID API.

מסקנות

בזכות הקושחה החדשה, אפשר להשתמש בשלט של Stadia כשלט משחק רגיל עם 17 לחצנים, וברוב המקרים זה יותר ממספיק כדי לשלוט במשחקי אינטרנט נפוצים. אם מסיבה כלשהי אתם צריכים נתונים מכל 19 הלחצנים בבקר, WebHID מאפשר לכם לקבל גישה לדוחות קלט ברמה נמוכה, שתוכלו לפענח אותם באמצעות הנדסה הפוכה של כל אחד מהם. אם במקרה כתבתם מנהל התקן מלא של WebHID אחרי שקראתם את המאמר הזה, אתם מוזמנים ליצור איתי קשר ואשמח לקשר את הפרויקט שלכם כאן. בהצלחה עם WebHID!

תודות

המאמר הזה נבדק על ידי François Beaufort.