กำลังพูดคุยกับ Stadia Controller ด้วย WebHID

Stadia Controller ที่แฟลชจะทํางานเหมือนเกมแพดมาตรฐาน ซึ่งหมายความว่าปุ่มบางปุ่มจะเข้าถึงไม่ได้โดยใช้ Gamepad API ตอนนี้คุณเข้าถึงปุ่มที่หายไปได้แล้วด้วย WebHID

เมื่อ Stadia ปิดตัวลง หลายคนกลัวว่าตัวควบคุมจะกลายเป็นฮาร์ดแวร์ไร้ประโยชน์ที่กองขยะ แต่โชคดีที่ทีม Stadia ตัดสินใจที่จะเปิด Stadia Controller แทนโดยให้เฟิร์มแวร์ที่กำหนดเองซึ่งคุณสามารถแฟลชลงในตัวควบคุมได้โดยไปที่หน้าโหมดบลูทูธของ Stadia ซึ่งจะทำให้ Stadia Controller ปรากฏเป็นเกมแพดมาตรฐานที่คุณสามารถเชื่อมต่อผ่านสาย USB หรือแบบไร้สายผ่านบลูทูธ หน้า Stadia Bluetooth แสดงอยู่ใน Project Fugu API Showcase อย่างภาคภูมิใจ โดยหน้าดังกล่าวใช้ WebHID และ WebUSB แต่นี่ไม่ใช่หัวข้อของบทความนี้ ในโพสต์นี้ เราต้องการอธิบายวิธีพูดคุยกับ Stadia Controller ผ่าน WebHID

Stadia Controller เป็นเกมแพดมาตรฐาน

หลังจากแฟลชแล้ว ตัวควบคุมจะปรากฏเป็นเกมแพดมาตรฐานต่อระบบปฏิบัติการ ดูภาพหน้าจอต่อไปนี้เพื่อดูการจัดเรียงปุ่มและแกนที่พบได้ทั่วไปบนเกมแพดมาตรฐาน ตามที่กำหนดไว้ในข้อกำหนดของ Gamepad API เกมแพดมาตรฐานจะมีปุ่มตั้งแต่ 0 ถึง 16 รวมเป็น 17 ปุ่ม (ปุ่ม D-Pad จะนับเป็น 4 ปุ่ม) หากลองใช้ Stadia Controller ในการสาธิตโปรแกรมทดสอบเกมแพด คุณจะเห็นว่ามันใช้งานได้ดี

สคีมาของเกมแพดมาตรฐานที่มีป้ายกำกับแกนและปุ่มต่างๆ

แต่หากนับปุ่มบน Stadia Controller จะมีทั้งหมด 19 ปุ่ม หากลองทีละปุ่มอย่างเป็นระบบในโปรแกรมทดสอบเกมแพด คุณจะพบว่าปุ่ม Assistant และ Capture ไม่ทำงาน แม้ว่าแอตทริบิวต์ buttons ของเกมแพดตามที่ระบุไว้ในข้อกำหนดของเกมแพดจะเป็นแบบเปิด แต่เนื่องจาก Stadia Controller ปรากฏเป็นเกมแพดมาตรฐาน ระบบจะแมปเฉพาะปุ่ม 0-16 เท่านั้น คุณยังคงใช้ปุ่มอื่นๆ ได้ แต่เกมส่วนใหญ่จะไม่รองรับปุ่มเหล่านั้น

WebHID เข้ามาช่วยแก้ปัญหา

คุณสามารถพูดคุยกับปุ่ม 17 และ 18 ที่หายไปได้โดยใช้ WebHID API และหากต้องการ คุณยังดูข้อมูลเกี่ยวกับปุ่มและแกนอื่นๆ ทั้งหมดที่มีอยู่แล้วผ่าน Gamepad API ได้ด้วย ขั้นตอนแรกคือการดูว่า Stadia Controller รายงานตัวเองไปยังระบบปฏิบัติการอย่างไร วิธีหนึ่งในการทำเช่นนี้คือการเปิดคอนโซลเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome ในหน้าใดก็ได้ และขอรายการอุปกรณ์ที่ไม่มีการกรองจาก WebHID API จากนั้นคุณเลือก Stadia Controller ด้วยตนเองเพื่อรับการตรวจสอบเพิ่มเติม รับรายการอุปกรณ์ที่ไม่มีการกรองโดยเพียงแค่ส่งอาร์เรย์ตัวเลือก filters ที่ว่างเปล่า

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

ในเครื่องมือเลือก รายการที่ 2 สุดท้ายจะมีลักษณะคล้ายกับ Stadia Controller

เครื่องมือเลือกอุปกรณ์ WebHID API ที่แสดงอุปกรณ์ที่ไม่เกี่ยวข้องบางรายการ และ Stadia Controller ในตำแหน่งสุดท้าย

หลังจากเลือกอุปกรณ์ "Stadia Controller rev. A" แล้ว ให้บันทึกออบเจ็กต์ HIDDevice ที่แสดงในคอนโซล ซึ่งจะแสดง productId (37888 ซึ่งก็คือ 0x9400 ในฐาน 16) และ vendorId (6353 ซึ่งก็คือ 0x18d1 ในฐาน 16) ของ Stadia Controller หากค้นหา vendorID ในตารางรหัสผู้ให้บริการ USB อย่างเป็นทางการ คุณจะเห็น 6353 แมปกับสิ่งที่คุณคาดไว้ ซึ่งก็คือ Google Inc.

คอนโซลเครื่องมือสำหรับนักพัฒนาเว็บของ Chrome ที่แสดงเอาต์พุตของการบันทึกออบเจ็กต์ HIDDevice

อีกวิธีหนึ่งนอกเหนือจากขั้นตอนที่อธิบายข้างต้นคือการไปที่ chrome://device-log/ ในแถบ URL, กดปุ่มล้าง, เสียบ Stadia Controller แล้วกดรีเฟรช ซึ่งจะให้ข้อมูลเดียวกันกับคุณ

อินเทอร์เฟซการแก้ไขข้อบกพร่อง chrome://device-log ที่แสดงข้อมูลเกี่ยวกับ Stadia Controller ที่เสียบอยู่

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

ใช้รหัส vendorId และ productId 2 รหัสนี้เพื่อปรับแต่งสิ่งที่แสดงในเครื่องมือเลือกโดยกรองหาอุปกรณ์ WebHID ที่ถูกต้อง

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

ตอนนี้สัญญาณรบกวนจากอุปกรณ์ที่ไม่เกี่ยวข้องทั้งหมดจะหายไป และมีเพียง Stadia Controller เท่านั้นที่แสดง

เครื่องมือเลือกอุปกรณ์ WebHID API ที่แสดงเฉพาะ Stadia Controller

ถัดไป ให้เปิด HIDDevice โดยการเรียกใช้เมธอด open()

await stadiaController.open();

บันทึก HIDDevice อีกครั้ง แล้วตั้งค่า Flag opened เป็น true

คอนโซล Chrome DevTools ที่แสดงเอาต์พุตของการบันทึกออบเจ็กต์ HIDDevice หลังจากเปิด

เมื่อเปิดอุปกรณ์ ให้รอฟังเหตุการณ์ inputreport ที่เข้ามาโดยแนบ Listener เหตุการณ์

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

เมื่อคุณกดปุ่ม Assistant บนตัวควบคุมแล้วปล่อย ระบบจะบันทึกเหตุการณ์ 2 รายการลงในคอนโซล คุณสามารถมองเหตุการณ์เหล่านี้เป็นเหตุการณ์ "ปุ่ม Assistant กดลง" และ "ปุ่ม Assistant กดขึ้น" นอกจาก timeStamp แล้ว เหตุการณ์ 2 รายการนี้ดูไม่แตกต่างกันเลยในตอนแรก

คอนโซลเครื่องมือสำหรับนักพัฒนาเว็บของ Chrome ที่แสดงการบันทึกออบเจ็กต์ HIDInputReportEvent

พร็อพเพอร์ตี้ reportId ของอินเทอร์เฟซ HIDInputReportEvent จะแสดงผลคำนำหน้าการระบุ 1 ไบต์สำหรับรายงานนี้ หรือ 0 หากอินเทอร์เฟซ HID ไม่ได้ใช้รหัสรายงาน ในกรณีนี้คือ 3 ข้อมูลลับอยู่ในพร็อพเพอร์ตี้ data ซึ่งแสดงเป็น DataView ขนาด 10 DataView มีอินเทอร์เฟซระดับต่ำสำหรับการอ่านและเขียนตัวเลขหลายประเภทในรูปแบบไบนารี ArrayBuffer วิธีทำให้การแสดงผลนี้เข้าใจง่ายขึ้นคือสร้าง Uint8Array จาก ArrayBuffer เพื่อให้คุณเห็นจำนวนเต็ม 8 บิตแบบไม่ลงนามแต่ละรายการ

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

เมื่อบันทึกข้อมูลเหตุการณ์ของรายงานอินพุตอีกครั้ง ทุกอย่างก็เริ่มมีความหมายมากขึ้น และเหตุการณ์ "ปุ่ม Assistant กดลง" และ "ปุ่ม Assistant กดขึ้น" ก็เริ่มอ่านออกได้ จำนวนเต็มแรก (8 ในทั้ง 2 เหตุการณ์) ดูเหมือนจะเกี่ยวข้องกับการกดปุ่ม และจำนวนเต็มที่สอง (2 และ 0) ดูเหมือนจะเกี่ยวข้องกับการกดปุ่ม Assistant หรือไม่

คอนโซลเครื่องมือสำหรับนักพัฒนาเว็บของ Chrome ที่แสดงออบเจ็กต์ Uint8Array ที่บันทึกไว้สำหรับ HIDInputReportEvent แต่ละรายการ

กดปุ่มจับภาพแทนปุ่ม Assistant แล้วคุณจะเห็นจำนวนเต็มลำดับที่ 2 สลับจาก 1 เมื่อกดปุ่มเป็น 0 เมื่อปล่อยปุ่ม ซึ่งจะช่วยให้คุณเขียน "ไดรเวอร์" ที่เรียบง่ายมากซึ่งช่วยให้คุณใช้ปุ่ม 2 ปุ่มที่หายไปได้

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 Controller ด้วย WebHID ได้ทีละปุ่มทีละแกน เมื่อเข้าใจแล้ว ที่เหลือก็แทบจะเป็นงานแมปจำนวนเต็มแบบกลไก

สิ่งที่ขาดหายไปตอนนี้คือประสบการณ์การเชื่อมต่อที่ราบรื่นซึ่ง Gamepad API มีให้ แม้ว่าคุณจะต้องผ่านประสบการณ์การใช้งานเครื่องมือเลือกครั้งแรก 1 ครั้งเสมอเพื่อใช้งานกับอุปกรณ์ WebHID เช่น Stadia Controller แต่คุณจะเชื่อมต่อกับอุปกรณ์ที่รู้จักอีกครั้งได้สำหรับการเชื่อมต่อในอนาคตเนื่องด้วยเหตุผลด้านความปลอดภัย โดยเรียกใช้เมธอด getDevices()

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

สาธิต

คุณจะเห็น Stadia Controller ที่ควบคุมโดย Gamepad API และ WebHID API ร่วมกันในเดโมที่เราสร้างขึ้น อย่าลืมดูซอร์สโค้ดซึ่งสร้างขึ้นจากตัวอย่างข้อมูลในบทความนี้ เราจะแสดงเฉพาะปุ่ม 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 Controller ใช้เป็นเกมแพดมาตรฐานที่มีปุ่ม 17 ปุ่มได้ ซึ่งในกรณีส่วนใหญ่แล้วถือว่าเพียงพอที่จะควบคุมเกมบนเว็บทั่วไป หากต้องการข้อมูลจากปุ่มทั้ง 19 ปุ่มบนตัวควบคุมไม่ว่าด้วยเหตุผลใดก็ตาม WebHID จะช่วยให้คุณเข้าถึงรายงานอินพุตระดับต่ำซึ่งคุณสามารถถอดรหัสได้โดยการทำวิศวกรรมย้อนกลับทีละรายการ หากคุณเขียนโปรแกรมควบคุมอุปกรณ์ WebHID ที่สมบูรณ์หลังจากอ่านบทความนี้ โปรดติดต่อเราและเรายินดีที่จะลิงก์โปรเจ็กต์ของคุณที่นี่ ขอให้สนุกกับ WebHID

ขอขอบคุณ

บทความนี้ผ่านการตรวจสอบโดย François Beaufort