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

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

นับตั้งแต่ที่ Stadia ปิดตัวลง หลายคนกลัวว่าตัวควบคุมจะกลายเป็นชิ้นส่วนของฮาร์ดแวร์ที่ไร้ประโยชน์ในหลุมฝังกลบ โชคดีที่ทีม Stadia ตัดสินใจเปิด Stadia Controller แทนโดยให้มีเฟิร์มแวร์แบบกำหนดเอง ซึ่งคุณจะแฟลชในคอนโทรลเลอร์ได้โดยไปที่หน้าโหมดบลูทูธ Stadia ซึ่งจะทำให้ Stadia Controller มีลักษณะเหมือนเกมแพดมาตรฐาน ซึ่งคุณจะเชื่อมต่อผ่านสาย USB หรือแบบไร้สายผ่านบลูทูธได้ หน้าบลูทูธของ 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 และปุ่มจับภาพจะไม่ทำงาน แม้ว่าแอตทริบิวต์ buttons ของเกมแพดที่ระบุไว้ในข้อกำหนดของเกมแพดจะเป็นแบบปลายเปิด เนื่องจาก Stadia Controller มีลักษณะเป็นเกมแพดมาตรฐาน ระบบจะแมปเฉพาะปุ่ม 0–16 เท่านั้น คุณยังคงใช้ปุ่มอื่นๆ ได้ แต่เกมส่วนใหญ่จะไม่คิดว่าจะมีปุ่มเหล่านั้นอยู่

WebHID ช่วยเหลือคุณได้

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

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

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

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

หลังจากเลือกอุปกรณ์ "Stadia Controller rev. A" แล้ว ให้บันทึกออบเจ็กต์ HIDDevice ที่ได้ไปยังคอนโซล ซึ่งจะแสดง productId (37888 ซึ่งเป็น 0x9400 ในเลขฐาน 16) และ 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 แสดงเอาต์พุตของการบันทึกออบเจ็กต์ 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 (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 และปุ่มจับภาพ (ควบคุมโดย WebHID API) ใต้รูปภาพตัวควบคุม คุณจะเห็นข้อมูลดิบ WebHID ที่จะช่วยให้คุณเห็นปุ่มและแกนทั้งหมดในตัวควบคุมได้

แอปเดโมที่ https://stadia-controller-webhid-gamepad.glitch.me/ ซึ่งแสดงปุ่ม A, B, X และ Y ที่ Gamepad API ควบคุม รวมถึงปุ่ม Assistant และปุ่มจับภาพที่ WebHID API ควบคุม

บทสรุป

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

ข้อความแสดงการยอมรับ

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