12月なのでクリスマスソングにノるテンアゲなキーボードliteを実装！【WebHID API】 #iotlt


この記事は[IoTLTアドベントカレンダー1日目](https://qiita.com/advent-calendar/2020/iotlt)です！（先に断っておきますが自作キーボード話ではないです） 先日のIoTLT vol69で話したネタをアップデートした感じです。
関係ないけどついに、この季節がやってきましたか〜 2020年めちゃ早い

> IoTLT vol69 https://iotlt.connpass.com/event/192582/

## テンション爆上がりなキーボードがこちら

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">クリスマスソングに合わせてキーボードを光らせる! <a href="https://twitter.com/hashtag/iotlt?src=hash&amp;ref_src=twsrc%5Etfw">#iotlt</a> の <a href="https://twitter.com/hashtag/%E3%82%A2%E3%83%89%E3%83%99%E3%83%B3%E3%83%88%E3%82%AB%E3%83%AC%E3%83%B3%E3%83%80%E3%83%BC2020?src=hash&amp;ref_src=twsrc%5Etfw">#アドベントカレンダー2020</a> 向けのやつです。 <a href="https://t.co/pWqOULx8nX">pic.twitter.com/pWqOULx8nX</a></p>&mdash; 菅原のびすけ (@n0bisuke) <a href="https://twitter.com/n0bisuke/status/1333784873417379842?ref_src=twsrc%5Etfw">December 1, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


毎年アドベントカレンダーネタでクリスマスっぽい話をやろうかと思ってたけど、全然やれてなかったので今回はクリスマス曲に合わせてみました。

記事の後半に実際に試せる(MacBookProでしか試してませんが)ページのURLを記載してますので是非手元でも試してみてください！

ちなみに再生してる曲はこちらから

> https://www.youtube.com/watch?v=eYhkSC3gRno

## なんとブラウザでキーボードバックライトの制御

Webブラウザからキーボードバックライトを制御出来ています。

ブラウザからハードウェアが制御できるAPIは色々と試して来てますが、今回は特に面白いですね。

## WebHID API - Nintendo Switchも動かせるよ

HIDは`human interface devices`の略です。様々なヒューマンインターフェイスデバイスをJavaSciprtで制御出来るAPIです。

> 参考記事から引用: ヒューマン インターフェース デバイス（HID）には、新しすぎる、古すぎる、あまりにも一般的でないなどの理由で、システムのデバイス ドライバーからアクセスできないロングテールなものがあります。WebHID API は、デバイス固有のロジックを JavaScript で実装する方法を提供することで、この問題を解決します。

ヒューマンインターフェイスデバイスと言われてもよく分からないと思いますが、 **Nintendo Switchのコントローラー（Joy-Con）**や冒頭のデモ動画のような **キーボードのバックライト**の制御が出来ます。

> ![](https://webdev.imgix.net/hid/joycon-switch-photo.jpg)

> * 参考: [Chrome 86: フォーカス ハイライトの改善、WebHID など](https://developers-jp.googleblog.com/2020/09/chrome-86-webhid.html)
> * 参考: [Connecting to uncommon HID devices](https://web.dev/hid/)

詳細は参考記事を読んでみてください。

### 利用はChrome v86以上かつ、現状オリジントライアル

WebHID APIは現時点だと、Google Chrome v86以上で対応しています。

他のブラウザの対応状況は[Can I Use](https://caniuse.com/?search=webhid)を見て確認しましょう。

> ![](https://i.gyazo.com/b32e32f342ea720a2b141a70892e361c.png)
> 2020/12/1現在 まだ全然対応してない......
> https://caniuse.com/?search=webhid

見てみると全然対応してないですね、最新版のChromeでもアップデートしただけでは使えず、オリジントライアルという利用するオリジン(利用するサイト的な)の登録作業をし、利用するユーザーのChromeのフラグ設定を変えてやっと利用出来ます。

### Gamepad APIとの違いは?

[Gamepad API](https://developer.mozilla.org/ja/docs/Web/Guide/API/Gamepad)というものが既存で存在しましたが、Nintendo SwitchのJoy-Conが使えるとなると、このAPIとの違いは？と思う人もいるかもしれません。（この辺のハードウェア制御系のブラウザAPIをウォッチしてる人が少なさそうですが苦笑）

> ![](https://i.gyazo.com/28cbd31ad7cbbb2dc9164f9ffe3b0aab.png)

間違ってるかもですが僕が見ている印象だと、`Gamepad API`はインプットのみで`WebHID API`の方がやれることは多そうな印象でした。

* Gamepad API
	* `デバイス -> ブラウザ` のインプットのみ
	* ゲームパッドのみ
* WebHID API
	* `デバイス <-> ブラウザ` でインプットとアウトプット両方
	* ゲームパッド以外も対応

上位互換というよりかは、Gamepad APIはゲームパッドからのインプットに特化してる気がします。ちゃんと調べ切れてないので、認識間違ってたらコメント下さい。

## WebHID APIを利用してみよう

では実際にWebHID APIを利用してみましょう。

### 事前準備

最初にちょっと準備が必要です。

#### クライアント（ブラウザ）側

フラグを確認する必要があります。`chrome://flags/`にアドレスバーからアクセスしま、`#experimental-web-platform-features`のフラグを`enable`にしましょう。

> ![](https://i.gyazo.com/2a1627eb521ea69e30fbc860ea2de09b.png)

#### サーバー側、オリジンの申請

WebHID APIは、現状オリジントライアルというステータスで、利用するオリジンをGoogleに申請する必要があります。

[こちら](https://developers.chrome.com/origintrials/#/view_trial/1074108511127863297)からオリジントライアル申請します。

> ![](https://i.gyazo.com/e6b87daa7fcc767279a3d01b6d00ff19.png)

ドメインだけでなく、オリジンなのでちゃんと`https://`も入れましょう。

* パスは不要です。
	* 例えば`https://hogehoge.com/huga/index.html`で利用したい場合は`https://hogehoge.com`を登録すれば大丈夫です。
* SSLがマストです。
	* `http`は使えません、`https`がマストです。

無事に登録できると次のページでトークンが発行されます。

利用するページのHTMLファイルのmetaタグに以下のような指定をし、発行されたトークンを指定することでWebHID APIが利用出来ます。

```
<meta http-equiv="origin-trial" content="TOKEN_GOES_HERE">
```

##### 補足: npxで気軽にサーバー起動とトンネリング

オリジンの登録ということは、基本ホスティング環境ということになりますが、毎回デプロイしなおすのは手間なので、最近はserveとngrokの二つのコマンドをnpx経由でインストールせずに実行して手軽に試してます。

* ローカルサーバー起動

```bash
$ npx serve -l 4444
```

* トンネリングサーバー起動

```bash
$ npx ngrok http 4444
```

ngrokで発行されたオリジンをオリジントライアルに登録するとデプロイなどせずに手軽に試せます

### コードの実装

`index.html`と`script.js`を作成しました。

#### HTML側 - トークンの指定

先ほども紹介したように、`index.html`のhead内に以下を記載しましょう。

```
<meta http-equiv="origin-trial" content="TOKEN_GOES_HERE">
```

TOKEN_GOES_HEREの箇所は登録して発行されたトークンです。

> ![](https://i.gyazo.com/a4cbe05935a87c90c9fa3dda8beabb61.png)

#### JavaScript側 - navigator.hid

`script.js`は以下のコードで実行してみます。

利用できる状態だと、`navigator.hid`が利用出来ます。

```js
if ("hid" in navigator) {
    // The WebHID API is supported.
    console.log(`HIDが使えるよ`);
} else {
    console.log(`HIDが使えません`);
}
```

コンソールに`HIDが使えるよ`と表示されればOKです。

> ![](https://i.gyazo.com/c9847f29b07c2de8fb1cf65932dc67e1.png)

`https://~~`で登録してるのにブラウザで確認するときに`http://~~`になってるなど登録した条件と違う場合は動作しないので注意しましょう。

#### Nintendo Switchのコントローラーで試す

そのまま実行すると怒られるのでボタンを追加して、ボタンクリックからスタートするようにします。

> Uncaught (in promise) DOMException: Failed to execute 'requestDevice' on 'HID': Must be handling a user gesture to show a permission request.


web.devのサンプルコードからそのままという感じですが、Nintendo SwitchのJoy-Conは`vendorId`や`productId`が↓のような値なので指定しましょう。

> 参考: https://web.dev/hid/

```js
// Filter on devices with the Nintendo Switch Joy-Con USB Vendor/Product IDs.
const filters = [
    {
      vendorId: 0x057e, // Nintendo Co., Ltd
      productId: 0x2006 // Joy-Con Left
    },
    {
      vendorId: 0x057e, // Nintendo Co., Ltd
      productId: 0x2007 // Joy-Con Right
    }
];
  
const main = async () => {
    // Prompt user to select a Joy-Con device.
    const [device] = await navigator.hid.requestDevice({ filters });
};


if ("hid" in navigator) {
    // The WebHID API is supported.
    console.log(`HIDが使えるよ`);
    const btn = document.querySelector(`button`);
    btn.addEventListener('click', main);
}else{
    console.log(`HIDが使えません`);
}
```

Webサイトにアクセスし、スタートボタンを押すと接続済みのJoy-ConがHIDとして認識されます。

> ![](https://i.gyazo.com/3c2b6b5de0609870b4f13c5dad7b3017.png)

また、[このような](https://gist.github.com/n0bisuke/175b285db4ddf7cc50f7cf1d76d89ae9)実装まで繋げてあげると、 **Joy-Conのバイブレーション機能を利用出来ます。**

```js
// First, send a command to enable vibration.
// Magical bytes come from https://github.com/mzyy94/joycon-toolweb
const enableVibrationData = [1, 0, 1, 64, 64, 0, 1, 64, 64, 0x48, 0x01];
await device.sendReport(0x01, new Uint8Array(enableVibrationData));

// Then, send a command to make the Joy-Con device rumble.
// Actual bytes are available in the sample below.
const rumbleData = [ /* ... */ ];
await device.sendReport(0x10, new Uint8Array(rumbleData));
```

> 参考: https://web.dev/hid/#send-output-reports

`device.sendReport()`でデータ配列を送ってバイブを制御しています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/04bf67ac-6fb9-f9bc-d529-75d674fc802c.png" width="400px">

> デモは[こちらのIoTLT vol69の様子を](https://youtu.be/Jynrd0VbIig?t=7962)

ここのデータを変えて試してみたいですね。

#### キーボードバックライトで試す

> ![](https://webdev.imgix.net/hid/laptop-keyboard-photo.jpg)

同じようなアクセスの仕方で、キーボードのバックライトをJavaScriptから制御できます。

> ![スクリーンショット 2020-12-01 20.52.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/e042f618-5a7f-6f56-a65f-685306403979.png "スクリーンショット 2020-12-01 20.52.28.png")

vendorIdなどのパラメータも参考記事を元に指定します。

```js
const [device] = await navigator.hid.requestDevice({
  filters: [{ vendorId: 0x05ac, usage: 0x0f, usagePage: 0xff00 }]
});
```

リクエストすると`Keyboard Backlight`の許可を求められます。

```js
// Blink!
const reportId = 1;
for (let i = 0; i < 10; i++) {
  // Turn off
  await device.sendFeatureReport(reportId, Uint32Array.from([0, 0]));
  await waitFor(100);
  // Turn on
  await device.sendFeatureReport(reportId, Uint32Array.from([512, 0]));
  await waitFor(100);
}
```


## 音楽に合わせてキーボード光らせるのはこちらで試せます

最後ですが、ここまで試して、キーボードを光らせることが出来たので、音楽にノらせてパリピ感のある演出をしてみたいなと突発的に思いました。これが最初のデモになります。

Izm Logさんのブログを参考に、ビート検出のコードを利用させてもらい、 **ビート検出時にキーボードを光らせる** というプログラムを作ってみましたが、けっこういい感じの反応になりました。

> [P5.jsでAudio Visualizerを作ってみた！【ビート検知】- Izm Log](https://izm51.com/posts/p5-js-audio-visualizer/)


[デモ動画](https://twitter.com/n0bisuke/status/1333784873417379842)の最後にも載せてますが、指パッチンでの反応もいい感じです。

> ![スクリーンショット 2020-12-02 19.05.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/81e3dfa0-abca-62f0-b0d2-bed3414e947b.png "スクリーンショット 2020-12-02 19.05.51.png")

MacBookProでしか試せてませんが、こちら↓で試せます。

> https://mystifying-newton-aedc2e.netlify.app/

* マイクの利用を許可する
* リクエストのボタンを押す
* デバイス利用の許可を求められるので、`Keyboard Backlight`を選択
	* (MacBookPro以外の人はここでデバイスが表示されないかも)
* オープンのボタンを押す
* このページを開きながら、何か音楽を流す
	* この時、別タブでYoutubeを長そうとすると、このページの処理が止まってしまうので、一つのPC内で試す際は別タブではなく`別ウィンドウ`で開くようにしましょう。このページがアクティブになってる状態にして下さい。

## まとめ

色々と書きましたが、Chrome v86から利用できるようになったWebHID APIを利用してみたという記事です。

まだGoogle公式のサンプルを少しいじったくらいしか試せてませんが、キーボードのバックライトをいじれるのはけっこう楽しいです。

Nintendo Switchのコントローラーもまだまだ遊びきれてないのでまた少し触ってみたいなぁ

前回のIoTLT vol69の時の様子はこちらのYoutubeでも観れるのでぜひご覧ください！そしてチャンネル登録も是非！

> https://www.youtube.com/watch?v=Jynrd0VbIig&t=11s

次回は[12/15](https://iotlt.connpass.com/event/196360/)にIoTLTを実施しますのでこちらもよろしくお願いします！

明日は@3yaka4さんによる`ねこIoT`的な何かの記事です！
楽しみですね〜 :) 

少し早いですが、今年もありがとうございました！来年もよろしくお願いします！

それでは！