Open6

WebHID APIでJoy Conを使ってみる

thirdlfthirdlf

ハッカソンのネタ探しの時に見つけた。

HIDとは、Human Interface Deviceの略で人間からの入力を受け取ったり、コンピュータから人間に情報を出力したりするデバイスのことらしい。
例)キーボードやマウス、ゲームパッドなど

USBには、機種の種類を表すデバイスクラスとしてHIDクラスというものがあり、製品ごとに個別のドライバソフトを用意してなくても利用できる。

Bluetoothにも、HIDプロファイルというものがある。

WebHIDは、このHIDをブラウザ上で使えるAPIで対応しているブラウザは少ないが、夢が広がりそう。

https://e-words.jp/w/HID.html
https://developer.mozilla.org/ja/docs/Web/API/WebHID_API

thirdlfthirdlf

なんとWiiリモコンやJoy Conはbluetooth接続ができ、HIDデバイスとして認識されるためWebHIDを使うと色々できるみたい。

今回は、Joy Conを使っていく。
Joy Con、実は結構リッチなコントローラーで

HD振動
加速度センサー
ジャイロスコープ
NFCリーダー
IRカメラ

など要素が盛り込まれている

thirdlfthirdlf

流れとしては、
requestDevice でアクセスの許可

openで接続確立

sendreportで色々設定

inputreportでデータが送られてきた時にイベント発火

thirdlfthirdlf

MDNのexampleを動かしてみる

index.html

<body>
  <h1>
    webhid Joy Con
  </h1>
  <Button id="leftButton">Joy Con L</Button>
  <Button id="rightButton">Joy Con R</Button>
  <Button id="check">Check</Button>
  <Button id="disconnect">disconnect</Button>
  <script src="script.js"></script>

  <h1>加速度</h1>
  <p id="value">neko</p>
</body>

script.js

const leftFilter = [
  {
    vendorId: 0x057e,
    productId: 0x2006,
  },
];

const rightFilter = [
  {
    vendorId: 0x057e,
    productId: 0x2007,
  },
];

let leftJoycon, rightJoycon;

document.getElementById("leftButton").addEventListener('click', async () => {
  let devices = await navigator.hid.requestDevice({ filters: leftFilter });
  leftJoycon = devices[0];
  if (leftJoycon) {
    await leftJoycon.open();
    leftJoycon.addEventListener('inputreport', event => {
      console.log("Left Joy-Con Input Report:", event);
      console.log("Data:", event.data);
    });
  }
});

document.getElementById("rightButton").addEventListener('click', async () => {
  let devices = await navigator.hid.requestDevice({ filters: rightFilter });
  rightJoycon = devices[0];
  if (rightJoycon) {
    await rightJoycon.open();

    rightJoycon.addEventListener('inputreport', event => {
      console.log("Right Joy-Con Input Report:", event);
      console.log("Data:", event.data);
    });

    const enableVibrationData = [1, 0, 1, 64, 64, 0, 1, 64, 64, 0x48, 0x01];
    await rightJoycon.sendReport(0x01, new Uint8Array(enableVibrationData));

    const rumbleData = [ /* ... */];
    await rightJoycon.sendReport(0x10, new Uint8Array(rumbleData));
  }
});

document.getElementById("check").addEventListener('click', async () => {
  console.log(leftJoycon, rightJoycon);
});

document.getElementById("disconnect").addEventListener('click', async () => {
  await rightJoycon.close();
  await leftJoycon.close();
});

sendReportでデータ送る時、
sendReport(OUTPUT, new Uint8Array([1, 0, 1, 64, 64, 0, 1, 64, 64, subcommand, arg]))
でいけるみたい。

上記の例だと、振動を有効化するためのレポートを送っている

const enableVibrationData = [1, 0, 1, 64, 64, 0, 1, 64, 64, 0x48, 0x01];
await rightJoycon.sendReport(0x01, new Uint8Array(enableVibrationData));

OUTPUT 0x01
Rumble and subcommand.

Subcommand 0x48: Enable vibration
One argument of x00 Disable or x01 Enable.

enableにするために0x01をargにつけて送る

振動のデータを送る
const rumbleData = [ /* ... */];
await rightJoycon.sendReport(0x10, new Uint8Array(rumbleData));

OUTPUT 0x10
Rumble only. See OUTPUT 0x01 and "Rumble data" below.

thirdlfthirdlf

加速度センサーを使うためには、

subcommand 0x40に 0x01送ったあと、input reportモードにして、INPUT 0x30を送るとよさそう

Subcommand 0x40: Enable IMU (6-Axis sensor)
One argument of x00 Disable or x01 Enable.

OUTPUT 0x01
Rumble and subcommand.

Subcommand 0x03: Set input report mode
One argument:

INPUT 0x30
Standard full mode - input reports with IMU data instead of subcommand replies. Pushes current state @60Hz, or @120Hz if Pro Controller.

addEventListener('inputreport', event
これだと、ボタンが押された時しかreportが来なかったため oninputeventに変更

script.js

document.getElementById("leftButton").addEventListener('click', async () => {
  let devices = await navigator.hid.requestDevice({ filters: leftFilter });
  leftJoycon = devices[0];
  if (leftJoycon) {
    await leftJoycon.open();

    const enableIMU = [1, 0, 64, 64, 0, 1, 64, 64, 0x40, 0x01]
    leftJoycon.sendReport(0x01, new Uint8Array(enableIMU));

    await sleep(200);
    const standardFullMode = [1, 0, 64, 64, 0, 1, 64, 64, 0x03, 0x30]
    leftJoycon.sendReport(0x01, new Uint8Array(standardFullMode));

    leftJoycon.oninputreport = (event) => {
      const { data, device, reportId } = event;
      console.log("Left Joy-Con Input Report:", event);
      console.log("Data:", event.data);
    }
  }
});

あと、このデータを使いやすい形に変換してあげたら良さそう。

https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/imu_sensor_notes.md

また、madgwickフィルタを使えば6軸のセンサでいい感じに姿勢推定ができるみたい。