Trò chuyện với tay điều khiển Stadia bằng WebHID

Tay điều khiển Stadia ở chế độ nháy sẽ hoạt động như một tay cầm chơi game tiêu chuẩn, tức là bạn không thể truy cập vào tất cả các nút của tay điều khiển này bằng Gamepad API. Với WebHID, giờ đây bạn có thể truy cập vào các nút còn thiếu.

Kể từ khi Stadia ngừng hoạt động, nhiều người lo ngại rằng tay điều khiển này sẽ trở thành một phần cứng vô dụng trên bãi rác. May mắn thay, nhóm Stadia đã quyết định mở tay điều khiển Stadia bằng cách cung cấp một chương trình cơ sở tuỳ chỉnh mà bạn có thể cài đặt trên tay điều khiển bằng cách truy cập vào trang Chế độ Bluetooth của Stadia. Nhờ đó, tay điều khiển Stadia sẽ xuất hiện dưới dạng một tay cầm chơi game tiêu chuẩn mà bạn có thể kết nối qua cáp USB hoặc không dây qua Bluetooth. Tự hào khi xuất hiện trên Project Fugu API Showcase, trang Bluetooth của Stadia sử dụng WebHIDWebUSB, nhưng đây không phải là chủ đề của bài viết này. Trong bài đăng này, tôi muốn giải thích cách bạn có thể giao tiếp với tay điều khiển Stadia thông qua WebHID.

Tay cầm Stadia là một tay cầm chơi game tiêu chuẩn

Sau khi nhấp nháy, bộ điều khiển sẽ xuất hiện dưới dạng gamepad tiêu chuẩn đối với hệ điều hành. Hãy xem ảnh chụp màn hình sau đây để biết cách bố trí nút và trục thường gặp trên một tay cầm chơi game tiêu chuẩn. Theo quy cách Gamepad API, các tay cầm chơi game tiêu chuẩn có các nút từ 0 đến 16, tức là tổng cộng 17 nút (d-pad được tính là 4 nút). Nếu dùng thử tay điều khiển Stadia trên bản minh hoạ trình kiểm thử tay cầm chơi game, bạn sẽ thấy tay điều khiển này hoạt động rất tốt.

Sơ đồ của một tay cầm chơi game tiêu chuẩn có các trục và nút được gắn nhãn.

Tuy nhiên, nếu bạn đếm các nút trên tay điều khiển Stadia, thì sẽ có 19 nút. Nếu thử từng nút một trong trình kiểm thử tay cầm chơi game, bạn sẽ nhận thấy nút Trợ lý và nút Chụp không hoạt động. Ngay cả khi thuộc tính buttons của tay điều khiển trò chơi như được xác định trong Thông số kỹ thuật của tay điều khiển trò chơi là không giới hạn, vì tay điều khiển Stadia xuất hiện dưới dạng một tay điều khiển trò chơi tiêu chuẩn, nên chỉ các nút từ 0 đến 16 được ánh xạ. Bạn vẫn có thể dùng các nút khác, nhưng hầu hết các trò chơi sẽ không mong đợi những nút này tồn tại.

WebHID góp sức

Nhờ API WebHID, bạn có thể tương tác với các nút 17 và 18 bị thiếu. Và nếu muốn, bạn thậm chí có thể lấy dữ liệu về tất cả các nút và trục khác đã có sẵn thông qua Gamepad API. Bước đầu tiên là tìm hiểu cách tay điều khiển Stadia tự báo cáo cho hệ điều hành. Một cách để thực hiện việc này là mở Bảng điều khiển Công cụ của Chrome cho nhà phát triển trên một trang bất kỳ, rồi yêu cầu danh sách thiết bị không được lọc từ API WebHID. Sau đó, bạn chọn tay điều khiển Stadia theo cách thủ công để kiểm tra thêm. Nhận danh sách thiết bị chưa được lọc bằng cách chỉ cần truyền một mảng filters trống.

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

Trong bộ chọn, mục gần cuối trông giống như tay điều khiển Stadia.

Trình chọn thiết bị WebHID API cho thấy một số thiết bị không liên quan và tay cầm Stadia ở vị trí áp chót.

Sau khi chọn thiết bị "Stadia Controller rev. A", hãy ghi lại đối tượng HIDDevice kết quả vào Bảng điều khiển. Thao tác này sẽ cho thấy productId (37888, là 0x9400 ở dạng thập lục phân) và vendorId (6353, là 0x18d1 ở dạng thập lục phân) của tay điều khiển Stadia. Nếu tra cứu vendorID trong bảng mã nhận dạng nhà cung cấp USB chính thức, bạn sẽ thấy rằng 6353 liên kết với những gì bạn mong đợi: Google Inc..

Bảng điều khiển Công cụ cho nhà phát triển của Chrome cho thấy kết quả của việc ghi nhật ký đối tượng HIDDevice.

Một cách khác để thực hiện quy trình được mô tả ở trên là chuyển đến biểu tượng chrome://device-log/ trong thanh URL, nhấn vào nút Xoá, cắm tay điều khiển Stadia rồi nhấn vào nút Làm mới. Thao tác này sẽ cung cấp cho bạn thông tin tương tự.

Giao diện gỡ lỗi chrome://device-log cho thấy thông tin về tay cầm Stadia đã cắm.

Một lựa chọn khác là sử dụng công cụ HID Explorer. Công cụ này cho phép bạn khám phá thêm nhiều thông tin chi tiết về các thiết bị HID được kết nối với máy tính.

Sử dụng hai mã nhận dạng này (vendorIdproductId) để tinh chỉnh nội dung xuất hiện trong bộ chọn bằng cách lọc chính xác thiết bị WebHID phù hợp.

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

Giờ đây, tiếng ồn từ tất cả các thiết bị không liên quan đã biến mất và chỉ có tay cầm Stadia xuất hiện.

Bộ chọn thiết bị WebHID API chỉ hiển thị tay điều khiển Stadia.

Tiếp theo, hãy mở HIDDevice bằng cách gọi phương thức open().

await stadiaController.open();

Ghi lại HIDDevice một lần nữa và cờ opened được đặt thành true.

Bảng điều khiển Công cụ cho nhà phát triển của Chrome cho thấy kết quả của việc ghi nhật ký đối tượng HIDDevice sau khi mở đối tượng đó.

Khi thiết bị mở, hãy theo dõi các sự kiện inputreport đến bằng cách đính kèm một trình nghe sự kiện.

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

Khi bạn nhấn và thả nút Trợ lý trên bộ điều khiển, hai sự kiện sẽ được ghi vào Bảng điều khiển. Bạn có thể coi đây là các sự kiện "nhấn nút Trợ lý" và "nhả nút Trợ lý". Ngoài timeStamp, hai sự kiện này trông không khác gì nhau trong nháy mắt.

Bảng điều khiển Công cụ cho nhà phát triển của Chrome cho thấy các đối tượng HIDInputReportEvent đang được ghi nhật ký.

Thuộc tính reportId của giao diện HIDInputReportEvent sẽ trả về tiền tố nhận dạng một byte cho báo cáo này hoặc 0 nếu giao diện HID không sử dụng mã nhận dạng báo cáo. Trong trường hợp này, đó là 3. Khoá bí mật nằm trong thuộc tính data, được biểu thị dưới dạng DataView có kích thước là 10. DataView cung cấp một giao diện cấp thấp để đọc và ghi nhiều loại số trong ArrayBuffer nhị phân. Để có được thông tin dễ hiểu hơn từ biểu diễn này, bạn có thể tạo một Uint8Array từ ArrayBuffer để xem các số nguyên không dấu 8 bit riêng lẻ.

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

Khi đó, nếu bạn ghi lại dữ liệu sự kiện báo cáo đầu vào một lần nữa, mọi thứ sẽ trở nên rõ ràng hơn và các sự kiện "Nút Trợ lý ở trạng thái nhấn" và "Nút Trợ lý ở trạng thái nhả" sẽ bắt đầu trở nên dễ hiểu. Số nguyên đầu tiên (8 trong cả hai sự kiện) có vẻ liên quan đến việc nhấn nút, còn số nguyên thứ hai (20) có vẻ liên quan đến việc nút Trợ lý có được nhấn hay không.

Bảng điều khiển Công cụ cho nhà phát triển của Chrome cho thấy các đối tượng Uint8Array đang được ghi nhật ký cho mỗi HIDInputReportEvent.

Nhấn nút Capture (Chụp) thay vì nút Assistant (Trợ lý) và bạn sẽ thấy số nguyên thứ hai chuyển từ 1 khi nút được nhấn sang 0 khi nút được thả. Điều này cho phép bạn viết một "trình điều khiển" rất đơn giản để có thể sử dụng 2 nút còn thiếu.

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

Bằng cách sử dụng phương pháp kỹ thuật đảo ngược như thế này, bạn có thể tìm ra cách giao tiếp với tay điều khiển Stadia bằng WebHID, từng nút và từng trục. Khi bạn đã quen với việc này, phần còn lại gần như là công việc ánh xạ số nguyên cơ học.

Điều còn thiếu hiện tại là trải nghiệm kết nối mượt mà mà Gamepad API mang lại cho bạn. Mặc dù vì lý do bảo mật, bạn luôn cần phải trải qua quy trình chọn ban đầu một lần để làm việc với một thiết bị WebHID như tay điều khiển Stadia, nhưng đối với các kết nối trong tương lai, bạn có thể kết nối lại với các thiết bị đã biết. Hãy thực hiện việc đó bằng cách gọi phương thức getDevices().

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

Bản minh hoạ

Bạn có thể xem tay điều khiển Stadia do Gamepad API và WebHID API cùng kiểm soát trong một bản minh hoạ mà tôi đã tạo. Hãy nhớ xem mã nguồn, được xây dựng dựa trên các đoạn mã trong bài viết này. Để đơn giản, tôi chỉ hiển thị các nút A, B, XY (do Gamepad API kiểm soát), cũng như các nút Trợ lýChụp (do WebHID API kiểm soát). Bên dưới hình ảnh bộ điều khiển, bạn có thể thấy dữ liệu WebHID thô, nhờ đó, bạn có thể cảm nhận được tất cả các nút và trục trên bộ điều khiển.

Ứng dụng minh hoạ tay cầm Stadia cho thấy các nút A, B, X và Y được điều khiển bằng Gamepad API, còn nút Trợ lý và nút Chụp được điều khiển bằng WebHID API.

Kết luận

Nhờ phần mềm mới, giờ đây, bạn có thể dùng tay điều khiển Stadia làm tay cầm chơi game tiêu chuẩn với 17 nút. Trong hầu hết các trường hợp, số nút này là quá đủ để điều khiển các trò chơi web thông thường. Nếu vì bất kỳ lý do gì mà bạn cần dữ liệu từ cả 19 nút trên bộ điều khiển, thì WebHID cho phép bạn truy cập vào các báo cáo đầu vào cấp thấp mà bạn có thể giải mã bằng cách thiết kế ngược từng báo cáo một. Nếu bạn viết một trình điều khiển WebHID hoàn chỉnh sau khi đọc bài viết này, hãy nhớ liên hệ với tôi và tôi sẽ sẵn sàng liên kết dự án của bạn tại đây. Chúc bạn có những trải nghiệm vui vẻ với WebHID!

Lời cảm ơn

Bài viết này được François Beaufort xem xét.