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

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

מאז שהשירות של Stadia נסגר, רבים חששו שהשלט הרחוק יהפוך לחתיכת חומרה חסרת תועלת בזבל. למרבה המזל, צוות Stadia החליט לפתוח את השלט הרחוק ל-Stadia במקום זאת, על ידי מתן קושחת בהתאמה אישית שאפשר להטמיע בשלט. כדי לעשות זאת, עוברים לדף מצב Bluetooth של Stadia. כך השלט הרחוק ל-Stadia יופיע כגאמפאד רגיל שאפשר לחבר באמצעות כבל USB או באופן אלחוטי באמצעות Bluetooth. הדף של Stadia Bluetooth, שמוצג בגאווה ב-Project Fugu API Showcase, משתמש ב-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 ריק.

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

בבורר, הערך שלפני האחרון נראה כמו השלט הרחוק של Stadia.

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

אחרי שבוחרים את המכשיר 'Stadia Controller rev. A', מתעדים ביומן את האובייקט HIDDevice שנוצר במסוף. כך תוכלו לראות את הערכים של productId (37888, שהוא 0x9400 בחזקת 16) ושל vendorId (6353, שהוא 0x18d1 בחזקת 16) בשלט הרחוק של 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 נכנסים.

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

הדגמה (דמו)

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

אפליקציית הדגמה בכתובת https://stadia-controller-webhid-gamepad.glitch.me/ ‏, שבה הלחצנים A, ‏ B, ‏ X ו-Y נשלטים על ידי Gamepad API, והלחצנים Assistant ו-Capture נשלטים על ידי WebHID API.

מסקנות

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

תודות

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