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

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

เนื่องจาก Stadia ปิดตัวลง หลายคนจึงกังวลว่าตัวควบคุมจะกลายเป็นฮาร์ดแวร์ที่ไร้ประโยชน์ในกองขยะ โชคดีที่ทีม Stadia ตัดสินใจที่จะเปิด Stadia Controller แทน โดยการจัดเฟิร์มแวร์ที่กำหนดเองซึ่งคุณสามารถแฟลชในตัวควบคุมได้โดยไปที่หน้าโหมดบลูทูธของ Stadia การดำเนินการนี้จะทำให้ Stadia Controller ปรากฏเป็นเกมแพดมาตรฐานที่คุณเชื่อมต่อได้ผ่านสาย USB หรือแบบไร้สายผ่านบลูทูธ หน้า Bluetooth ของ Stadia ซึ่งแสดงอย่างภาคภูมิใจใน 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: []});

ในตัวเลือก รายการที่อยู่ก่อนสุดท้ายจะมีลักษณะเหมือน Stadia Controller

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

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

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

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

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

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

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

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

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

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

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

await stadiaController.open();

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

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

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

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

เมื่อคุณกดและปล่อยปุ่ม Assistant บนตัวควบคุม ระบบจะบันทึกเหตุการณ์ 2 รายการลงใน Console คุณสามารถคิดว่าเหตุการณ์เหล่านี้เป็นเหตุการณ์ "กดปุ่ม 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 (2 และ 0) ดูเหมือนจะเกี่ยวข้องกับการกดปุ่ม Assistant หรือไม่

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

กดปุ่มจับภาพแทนปุ่มผู้ช่วย แล้วคุณจะเห็นว่าจำนวนเต็มที่ 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) รวมถึงปุ่มผู้ช่วยและจับภาพ (ควบคุมโดย WebHID API) คุณจะเห็นข้อมูล WebHID ดิบใต้รูปภาพของคอนโทรลเลอร์ ซึ่งจะช่วยให้คุณทราบถึงปุ่มและแกนทั้งหมดในคอนโทรลเลอร์

แอปสาธิตของ Stadia Controller แสดงปุ่ม A, B, X และ Y ที่ควบคุมโดย Gamepad API รวมถึงปุ่ม Assistant และปุ่ม Capture ที่ควบคุมโดย WebHID API

บทสรุป

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

คำขอบคุณ

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