TypescriptでWebHIDを扱う

6 min read読了の目安(約5400字

WebHID+Typescriptについての簡単な紹介記事です.
chromeで設定不要で使えるようになる、との事で一部で話題になったりならなかったりしているものです.
気になる方や要望が多かったらもう少し具体的な実装まで踏み込んでまともなbookにします.
動機は不純で,zennやQではじまる所などの記事投稿サイトで拡散力のない人間がviewをのばすにはjs,web,reactについての記事を書くのが早いと聞いたからです.

はじめに

jsで使う記事は英語/日本語ともに記事が幾つかあるので、Typescriptの型付きのものについての記事を書きます.
簡単な紹介とtypescriptと使う場合の手順についての記載のみをします.
まだbookを書いていないので,素のJSから使う場合についてやjoyconなどの実際の具体的なコマンドについては、この記事よりも細かく踏み入った記事(記事末尾参照)を参考にしてください.

そもそもWebHIDって

w3cでdraftとして定義されている仕様で、シンプルに言えば「webサイトがbrowserからlocalのhidデバイスにアクセスする仕組み」です.

w3c WebHID draft

何が出来るか

  • jsからlocalにサーバーとなるプログラムやライブラリを用意することなくHIDデバイスを制御出来る.
    • Keyboardを自作する際の動作確認ツールをリッチなwebサイトとして実装する、とかですかね.
  • 今までGamepad APIなどのAPIで出来なかった通信や非対応だったデバイスの機能が使える.
    • 例えばNintendo Switchjoyconならジャイロの情報を得たりIRセンサーを取り扱ったり出来る.
    • 「wasm + WebHID でゲーム(エンジン)作りました」とかイケイケに見えません?(他人任せ)

注意

記事末尾の関連記事のほうぼうに記載されていますが、

WebHID有効化

  • Google Chrome

    • version 89 or later -> そのまま使えます.
    • version 88 or earlier
      • chrome://flags/#enable-experimental-web-platform-features をchromeに直接入力してアクセス.[1]
      • Experimental Web Platform features を enable にする.

    2020/02/14 現在でversion 89 beta以降のchromeを使っている人間は相当なモノ好きか職業戦士の方だと思いますので、多くの方はchrome://flagsを開いて設定する必要があると思います.

Typescriptと使う

導入

以前は独自に型定義をしないといけませんでした[2]が、最近DefenitelyTypedに型が追加されたのでそれを使うことが出来ます.

作成者のkkiyama117さんに感謝ですね......
w3c-web-hid をdependenciesに追加します.

yarn add --dev @types/w3c-web-hid

実装

今回は他の多くの例同様にNintendo switchのコントローラーを使用するシンプルな実装を用意しました.
実際に利用する場合は各OSの設定でbluetooth接続をする必要があります.
基本的に [コントローラーの新規接続用の操作](https://support.nintendo.co.jp/app/answers/detail/a_id/36558/)を行い、OSのbluetoothの設定からペアリングをするだけです.
(linuxでつながらない場合はudevを弄る必要があると思います.)
仕様は以下の通りです.

  • openをクリックしてコントローラーにHIDとして接続でき、sendでいくつかのコマンドをoutput出来ます.
  • そのままだとlogが貯まって重くなるのでcloseでdeviceを切断できます.
  • また, chrome dev toolsconsoleで通信によるデバイスからのinputが確認できます.
  • joycon/proconの両対応(のはず)です.

実際のcodeはこちらです.
(ReactのuseEffectcustom hook、consoleへの雑な出力ではない適切なreportの処理等をすべきですが、簡単なdemoにつき一部省略しています. この実装をそのまま使う際は注意してください.)

基本的なデバイスとの通信の流れは以下の通りです.

  1. navigator.hid.requestDevice/navigator.hid.getDevicesHIDDeviceをユーザーに指定させたり選択したりする.
  2. HIDDevice.openで接続を開く.
  3. HIDDevice.addEventListenerHIDDeviceからのinputを取得できるようにする.
  4. HIDDevice.sendReportHIDDeviceにreport(hidの通信の単位)を送信する.
demo.ts
// 1. `navigator.hid.requestDevice`/`navigator.hid.getDevices` で`HIDDevice`をユーザーに指定させたり選択したりする.
const devices: HIDDevice[] = await navigator.hid.requestDevice({
  filters: [{
    // deviceのvendorとproductのID
    //vendorId?: number;
    //productId?: number;
    vendorId: 0x057e,
    productId: 0x2009,
  }]
});
const device = devices[0];
if (device) {
  // 2. `HIDDevice.open`で接続を開く.
  await device.open();
  // 3. `HIDDevice.addEventListener`で`HIDDevice`からのinputを取得できるようにする.
  device.addEventListener("inputreport", (e)=>{console.log(e)}); // event handler
  // 4. `HIDDevice.sendReport` で`HIDDevice`にreport(hidの通信の単位)を送信する.
  // await device.sendReport(reportId :number, sendData: BufferSource);
  await device.sendReport(0x01, Uint8Array.from( ... ));
}

型定義があるのでこれだけでTypescriptでWebHIDを取り扱うことが出来ます.
後は必要なデバイスのHIDの規格さえ把握すれば簡単に処理することができますね.

終わりに

元々昨年(2020)末頃からwebHID+React関連の仕事をしていたところ、1月末から動きがあったので今回記事にしました.
記事を書くのって時間がすごいかかりますね. 出典が合っているかを再度確認するだけで数時間はかかります. 毎日書くような方にコツを聞きたいくらいです.
記事に間違いや誤字脱字があった際は優しく教えてくださると嬉しいです。
また、記事を良く読むと気づくのですが、実は僕がw3c-web-hidの型定義のcode ownerでもあるので、そちらに間違いがあった時も優しくIssueやPull Requestを投げてください.
型定義作成時にvim-jpslackで相談に乗ってくださった方へのお礼を持ちまして、この記事の〆とさせていただきます.

参考/関連

WebHID

joycon

news

脚注
  1. 余談ですが、直リンクとして書いたらchromeにはじかれてflagsのページを開けませんでした. ちゃんとしてるんですね. ↩︎

  2. 無い時はWebStormやvimのlsp,tscなんかがnavigatorなどの型と自前の型をなかなか同じものとして扱ってくれなかったりしてかなり大変でした) ↩︎