toioでパワポコントローラーを作る

これは「toio™(ロボットトイ | toio（トイオ）) Advent Calendar 2019」の19日目の記事になります。

## はじめに

仕事でPowerPoint（以下パワポ）を使うこと多いですよね。

私はPCの前でポチポチとパワポを操作して資料を説明するのが嫌でした。

> スクリーンの前でかっこよくプレゼンしてみたい。。
> PCから離れてプレゼンすれば、なんか"イケてる感"が出そうな気がする。。
> そうだ、パワポ操作用リモコンだ。
> それさえあれば、ボクもイケてるおっさんになれるハズ！

そんな想いを叶えるべく、手近にあった toio コアキューブを使って簡易パワポ操作リモコンを作ってみました。

（少し無理な感じのある導入おわり）

## 使用ハードウェア

* toio コアキューブ
* Windows10 (1909) が入っているノートPC （Bluetoothは内蔵）


## パワポコントローラー

作るべきパワポコントローラーについて考えます。
必要な機能は厳選したいです。

### 必要な機能の抽出

編集時にはいろんなことができるパワポですが、発表時の操作は極めてシンプルです。
イケてる発表動画を見るとわかります。カッコいいプレゼンには**シート送り機能**さえあればよいのです。ほかの余計な機能など一切不要です。

しかし現実には、

> 「ちょっとさっきのページ見せて」
> 「あ、わかりにくかったでしょうか？ すみませんもう一度説明します」

みたいなやりとりが当然のように発生するので、**シート戻し機能**も必要となります。

あと実際にパワポをプレゼンテーションモードで操作してわかったのですが、最終シートで迂闊にシート送り操作を行うとプレゼンテーションモードが終了してしまいます。

ここで資料の再表示を求められると致命的です。復帰のためにPCまで走って行って再びプレゼンテーションモードにしなくてはいけません。とてもカッコ悪いです。
正直どうしてこういう仕様なのか理解できません。最終シート保持のままでいいと思うのは私だけでしょうか。
文句を言っても仕方がありません。この状況を脱するための**プレゼンテーションモード開始機能**も必要です。

あと、なんとなく一番最初のシートに戻れたほうがいい気もしたので**最初のシートに戻る機能**も実装することにしました。

機能は厳選とか言っていたのに、なんだか微妙に増えている気がします。。


### 機能の実現方法

パワポでは上記の機能はすべて１キーで実現できます。

|機能|キー操作|
|----|:-------:|
|シート送り| <kbd>PageDown</kbd> |
|シート戻し| <kbd>PageUp</kbd> |
|プレゼンテーションモード開始| <kbd>F5</kbd> |
|最初のシートに戻る|<kbd>Home</kbd>|

つまり、**toio キューブの操作をキーイベントに変換**できれば目的達成となります。

### toio側の操作と機能の対応

toio キューブの操作と機能の対応を考えます。
公開されている[toio コアキューブ技術仕様](https://toio.github.io/toio-spec/)を見ると、[ボタン操作の検出](https://toio.github.io/toio-spec/docs/ble_button)ができることがわかります。また、[キューブの姿勢検出](https://toio.github.io/toio-spec/docs/ble_sensor#%E5%A7%BF%E5%8B%A2%E6%A4%9C%E5%87%BA)もできます。

これらを組み合わせて、以下のように作ってみたいと思います。

|toio キューブ操作|機能|キー操作|
|---|----|:-------:|
|逆さ位置 ＋ ボタンのシングルクリック |シート送り| <kbd>PageDown</kbd> |
|横位置 ＋ ボタンのシングルクリック |シート戻し| <kbd>PageUp</kbd> |
|ボタンのダブルクリック |プレゼンテーションモード開始| <kbd>F5<kbd> |
|ボタンの長押し（位置不問）|最初のシートに戻る|<kbd>Home</kbd>|

ダブルクリックの検出は toio キューブが持っている機能にないので、ここは実装でなんとかします。
（ダブルタップの検出はできますが、これはちょっと違うものです）

## さあ作るぞ

toioを使って何かする場合、[node.jsで実装された公式ライブラリ toio.js ](https://github.com/toio/toio.js/)がとても便利です。しかしtoio.jsはBLE制御に[noble](https://github.com/noble/noble)を使っている関係で、Windowsで利用する場合はUSBのBLEドングルが必要です。今回はBLEドングルを ~~買う資金的余裕がなかった~~ 用意したくなかったので、toio.jsは使いません。
winrt経由でWindowsのBLE APIを使えばノートPC内蔵のBluetoothが利用できるので、ドングルを用意しなくてもなんとかなります。

そして、今回のパワポコントローラーは[**Rust**](https://www.rust-lang.org/)を使って実装します。
winrtを簡単に使う時はC#を使うのが定石のような気がしますが、ここは敢えてRustで挑戦します。
とはいえ実は私はRustにもwinrtにもそれほど詳しくありません。


昔の人は言いました。
*「なぜ山に登るのか、そこに山があるからだ」*
同じ気持ちです。（たぶん。。）

> **「なぜRustで作るのか、そこにRustがあるからだ」**

というわけで、以下はRust素人が適当に語る話です。
何かちゃんとした役に立つことが書いてあるんじゃないかと期待した方ごめんなさい。



### Rust+winrtでBLE

[winrt-rust](https://docs.rs/winrt/0.6.0/winrt/)を使ってRustからwinrtのBLE APIを呼び出します。
Rust+winrt+BLEの話は、[Rustその3のアドベントカレンダー 19日目](https://qiita.com/kaz399/private/fe5247875fdf2003f83b)に書いたので詳細は省略します。興味のある方はそちらを読んでください。


### キューブの情報取得

キューブの情報はreadで読み出すことができます。
今回のように簡単な状態を知りたいだけなら周期的な読み出し処理で問題なさそうですが、~~notifyを使ったほうがなんかカッコいい感じがする~~ 読み出しタイミングとの関係で操作性が悪くなりそうな気がするのでnotifyを使ってみます。

ハンドラ側からメインの処理にいい感じでデータを渡す方法がよくわからず、lazy_staticとMutexでグローバル変数渡し的に解決しました。
readはダサいとか言っておきながら、結果notifyのほうが余計にダサくなってしまった気もします。

ボタンのnotifyハンドラ内では、ステータス（押された/離された）の他に時刻情報も付けてVecにボタン情報を積んでいきます。
この時刻情報を使ってダブルクリックや長押し検出をしています。
姿勢センサーのnotifyにはタップ情報やダブルタップ情報も含まれています。

ハンドラ側はひたすらVecに値を積むだけで、メインループ側で過去の情報を捨てる実装になっています。

コードでは[このあたり](https://github.com/kaz399/rs_toio_cube/blob/9e56847723e1fb88bef452fb2f70a59987a73c69/src/main.rs#L94-L161)です。



### ボタンのダブルクリックと長押し

ダブルクリックや長押しは
**「一定期間にどんなボタンのイベントがあったのか」**
を見て決めています。

各動作に対しての検出期間があり、この期間内に動作が完了すればイベント発生です。

|イベント|検出期間|
|---|---|
|ダブルクリック、シングルクリック|500[ms]|
|長押し|1500[ms]|

ボタンのイベント生成処理はメインループから周期的に呼び出しています。
ボタンの状態変化と時刻情報履歴がVecに保存されているため、イベント生成処理の呼び出し周期とボタンの状態検出期間は同期の関係にありません。
readでボタン情報を取得する場合は、このあたりの処理の周期管理をしっかり行って状態遷移させる実装が思いつきますが、私は状態遷移の実装が ~~とてもへたくそ~~ あまり好きではないので避けました。

短い間隔でクリックするとCube側でうまくボタンのオン・オフが検出できないようです。そのためダブルクリックは若干ゆっくりとした操作が必要になりました。


処理は[こんな感じ](https://github.com/kaz399/rs_toio_cube/blob/9e56847723e1fb88bef452fb2f70a59987a73c69/src/main.rs#L328-L391)です。やっていることはかなりベタです。


### キーイベント

キーイベント送出には[enigo](https://docs.rs/enigo/0.0.13/enigo/)パッケージを使いました。
Exampleを見る限りでは使い方も簡単そうだったのですが、enigoにはハマりポイントがありました。
enigoの矢印キーやPageUp/Downキーは**NumLockされていない状態のテンキー**のイベントでした。
独立キーのほうを送るためには拡張コードとして送らなければいけないようですがenigoはそうなっていません。
NumLock状態でキーイベントを送ると**3とか7とかの数字**が送られてしまいます。これはガッカリです。
enigo側を修正すれば直りそうですが、どのキーを拡張コードにするべきなのか詳しい情報が不明だったのでやめました。今はとりあえずパワーポイントを操作したい気持ちを優先してNumLockをオフにして耐えます。いわゆる**運用でカバー**というやつです。

参考：
https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-keybd_event
https://ze-key.blogspot.com/2016/08/keyeventfextendedkey.html

## できた

いろいろ試しながらとりあえず完成しました。
コードは[GitHub](https://github.com/kaz399/rs_toio_cube)にあります。
バイナリも[GitHub](https://github.com/kaz399/rs_toio_cube/releases/tag/v0.1.0)にあります。しかしこれは個人的なお試し実装なので、このパワポコントローラーを使用したことに基づくいかなる損害（プレゼンの失敗を含む）についても作者は一切の責を負いません。

### ビルド方法

Rust的にいつもの方法でビルドできます。

```bash
git clone https://github.com/kaz399/rs_toio_cube.git
cargo build
cargo run
``` 

### つかいかた

1. キューブとWindows PCをペアリングしておきます
2. キーボードのNumLockをオフにします（超重要）
3. cubekey.exeを起動します
4. 接続できたらパワポ資料を開いてプレゼンを行ってください


詳しくは[GitHubにある説明ドキュメント](https://github.com/kaz399/rs_toio_cube/blob/master/cubekey.md)をご覧ください。

## その他お遊び機能

機能は厳選とか言っていましたが、やっぱりお遊びも入れてみたくなりました。



### お遊び機能その１

ログをデバッグに設定して動かすと[専用マットの座標](https://toio.github.io/toio-spec/docs/ble_id#position-id)を表示します。
デバッグ状態で起動する方法は下記です。

```bash
RUST_LOG=debug cargo run
```

### お遊び機能その２

キューブを置いてダブルタップ（軽く2回コツコツたたく）すると、どこかで聞いたファンファーレが流れてキューブが回転します。


## おわりに

とりあえずパワポコントローラーができました。winrt初挑戦でしたが何とか動くものができました。
~~これは**toio史上初の実用プロダクト**ではないでしょうか！~~ <sub>（訂正：過去にtoioキューブを利用したラーメンタイマーが存在していたとの情報を得ました。史上初ではありませんでした。）</sub>
作ってみて思いのほか楽しかったので、次は別のリモコン的な応用も考えてみたいと思います。

それでは皆様もよいクリスマスを。


<sub>**追記**</sub>
<sub>このパワポコントローラーを使ったプレゼンの結果、詳しくは書きませんが
**<font color=red>大切なのは小道具ではなく内容である</font>**
という基本中の基本を改めて思い知ることができました。おわり。</sub>