Androidアプリで操作できるってきいたんでLinux PCから操作できないわけはないだろうということでためしてみる
まずはベースステーションについて
一言にSteamVRデバイス、といっても様々なデバイスが存在しますが、そのなかでもValve IndexやVive Proなどは標準でValveのLighthouse2.0を使用しています。これらのフルセットを買うとついてくるのが所謂ベースステーション2.0。
SteamVRはその仕組み上、これがなければはじまりません。
さて、このベースステーションですが、内部ではモーターが高速で回転しており起動中は高周波の音を発生させています。VRのプレイ中はたいして気にはなりませんが、普段はその騒音がかなりきになるところ。また、これによって衝撃による故障も発生しやすくなっています。困った子で、私も初回購入時には1週間たたずに死の赤点滅にみまわれRMAに……。
そんなベースステーションですが、(当然使用者の方であればご存知でしょうが) SteamVR から電源管理を行うことができます。ベースステーションには通常起動の状態以外にスリープとスタンバイの電源状態が用意されているのです。
ベースステーションの電源管理にはBluetooth (あとで重要ですがBLE)を使用します。
しかし、私の知る限りSteamVRからSteamVRの開始/終了にあわせてこの電源管理を使用するにはHMDやリンクボックスに内蔵されているBTコントローラーのみが使用できるようです。ちなみにこのBTコントローラーは(少くともIndexでは)いずれかのVRコントローラーと無線アンテナを(?)共有しており、排他使用になっているようです。
しかし、HMDをHMDと使用していない状態ではたとえHMDのUSBのみが接続されていようが、PCにBluetoothのドングルが接続されていようが、SteamVRでBluetoothを利用することはできないようです。
普通の使い方をしていればこれで問題ないのかもしれませんが、一部の特殊な使い方をしていると地味に不便な話です。そう、たとえばHMDにはOculus Questを使いつつコントローラーやトラッカーにLighthouseを使用する我々には。
そもそも稀に電源管理が正しく機能しないこともあるのでスマートコンセントを使用してそもそものコンセントの制御をGoogleアシスタントで完結するようにしているのですが、ちゃんと仕組みを知って自分で操作したいと思うのが開発者心。
参考になりそうなAndroidアプリの存在
ベースステーションをSteamVR以外から操作できる先行事例として、Lighthouse Power ManagementというAndroidアプリが公開されているようです。うれしいことに、このアプリはGPL3+のオープンソースソフトウェアであり、GitHubで公開されています。なおflutter製。
Androidアプリとはいったものの、どうやらiOSやLinux、ましてやWeb bluetoothでも動作したり、なんならWindowsサポートも準備中のようです。もうこれでいいのでは
このアプリはSteamVR2.0だけでなくてLighthouse1.0、初代Viveのほうもサポートしています。万能。操作したいだけならこれつかえばいいや!
や、BLEを完全に理解したいんだ!(無茶)
あと、How to Power Off Basestations remotely [SOLVED] – Pimax Hardware / Pimax Discussion – OpenMR | Communityっていうスレッドも参考になりそう。
先人の調査に感謝。
LinuxのCLIからBSを操作する
やっとこさ本題。
とりあえず前述のアプリのソースコードをながめると、BaseStationとの通信をBLE でおこなっていることがよくわかります。なかでもLighthouse2.0のBSの定義をしているのがこのクラス。あとで色々と参考にしましょうね。
おもにこれを参考に、コマンドラインでベースステーションを操作していきます。
環境
さて、今回の実験を行う環境の前提条件は以下のとおり。いつもの環境。
- ThinkPad X395 (Ryzen 5 PRO 3500U)
- ArchLinux (5.13.12-AMD x86_64 GNU/Linux)
- BT: Intel Wireless-AC 9260
内蔵のWLANカードを利用していますが、今時のBTドングル等もBLEは対応してそうなのでとりあえずBLEの使えるものがあればOK。
今回はBlueZのBLE機能をレガシーAPIから使用するため、 bluez-utils
ではなく、aurから bluez-utils-compat
をインストールしておきます。最終的にはいらなかったんだけどbtgatt-clientわかりにくい部分もあったので。
1 |
$ yay -S bluez-utils-compat |
既にbluez-utilsがインストールされている環境ではリプレースすることになりますね。
今回用いるレガシーなAPIについてはこちらのエントリがいいかんじに参考になりました。
BLEデバイスのスキャン
まずはBSをみつけるため、BTデバイスをスキャンします。
1 2 3 4 5 |
$ sudo hcitool lescan FD:BE:C1:xx:xx:xx LHB-15xxxxxx D4:2C:B1:xx:xx:xx LHB-EBxxxxxx 59:16:65:xx:xx:xx (unknown) ... |
hcitool
ではうまくうごかない場合もありました。普通にbluetoothctl
で大丈夫。
1 2 3 4 5 6 7 8 9 10 11 |
$ bluetoothctl Agent registered [bluetooth]# scan on Discovery started [CHG] Controller 28:7F:CF:xx:xx:xx Discovering: yes [NEW] Device FD:BE:C1:xx:xx:xx LHB-15xxxxxxxx [NEW] Device 00:1A:7D:xx:xx:xx hogehoge [NEW] Device D4:2C:B1:xx:xx:xx LHB-EBxxxxxx ... [bluetooth]# scan off Discovery stopped |
BLEを使用するのにペアリングの必要はありません。Coke On PayやLINEのBLEビーコンだってそうでしょ?とりあえずMACアドレスをひかえておきましょう。
接続してサービスをみつける
MACアドレスがわかったところで早速接続してみます。BLEのGATT通信ってやつを使うことでBLEデバイスとデータがやりとりできるようです。レガシーAPIですが今回はまずgatttool
を使用します。ざっくりコマンドの確認もしておきましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
sudo gatttool -b D4:2C:B1:xx:xx:xx -t random -I [D4:2C:B1:xx:xx:xx][LE]> help help Show this help exit Exit interactive mode quit Exit interactive mode connect [address [address type]] Connect to a remote device disconnect Disconnect from a remote device primary [UUID] Primary Service Discovery included [start hnd [end hnd]] Find Included Services characteristics [start hnd [end hnd [UUID]]] Characteristics Discovery char-desc [start hnd] [end hnd] Characteristics Descriptor Discovery char-read-hnd <handle> Characteristics Value/Descriptor Read by handle char-read-uuid <UUID> [start hnd] [end hnd] Characteristics Value/Descriptor Read by UUID char-write-req <handle> <new value> Characteristic Value Write (Write Request) char-write-cmd <handle> <new value> Characteristic Value Write (No response) sec-level [low | medium | high] Set security level. Default: low mtu <value> Exchange MTU for GATT/ATT |
あ、まだこの時点では接続してないんです。接続してプライマリサービスの一覧をみてみましょう。接続できるとMACアドレスの部分が青くなるようです。
1 2 3 4 5 6 7 8 9 10 |
[D4:2C:B1:xx:xx:xx][LE]> connect Attempting to connect to D4:2C:B1:xx:xx:xx Connection successful [D4:2C:B1:xx:xx:xx][LE]> primary attr handle: 0x0001, end grp handle: 0x0009 uuid: 00001800-0000-1000-8000-00805f9b34fb attr handle: 0x000a, end grp handle: 0x000d uuid: 00001801-0000-1000-8000-00805f9b34fb attr handle: 0x000e, end grp handle: 0x0016 uuid: 00001523-1212-efde-1523-785feabcd124 attr handle: 0x0017, end grp handle: 0x001a uuid: 00000000-0060-7990-5544-1cce81af42f0 attr handle: 0x001b, end grp handle: 0x001e uuid: 0000fe59-0000-1000-8000-00805f9b34fb attr handle: 0x001f, end grp handle: 0xffff uuid: 0000180a-0000-1000-8000-00805f9b34fb |
…UUIDだけみても正直それがなにをさしている値なのかはよくわかりません。ここで参考資料の出番です。どうやら3つめの00001523-1212-efde-1523-785feabcd124
がコントロールを司っているサービスのようです。(ほかはなにしてるんでしょう、FWのアップデートとかも関係あるはず……)
では、当該のサービスの中身をみてみます。
1 2 3 4 5 |
[D4:2C:B1:xx:xx:xx][LE]> characteristics 0x0e 0x16 handle: 0x000f, char properties: 0x08, char value handle: 0x0010, uuid: 00008421-1212-efde-1523-785feabcd124 handle: 0x0011, char properties: 0x1a, char value handle: 0x0012, uuid: 00001525-1212-efde-1523-785feabcd124 handle: 0x0014, char properties: 0x1a, char value handle: 0x0015, uuid: 00001524-1212-efde-1523-785feabcd124 |
たしかに参考コードでみたUUIDがでてきました。上から順に識別用(0x10)、電源管理(0x12)、チャンネル管理(0x15)のようです。
もっとも、今回はUUIDがわかっているのでこんなことをせずともUUIDからツールにまかせてハンドルや値が取得できます。
1 2 3 |
[D4:2C:B1:xx:xx:xx][LE]> char-read-uuid 00001525-1212-efde-1523-785feabcd124 handle: 0x0012 value: 0b |
電源状態は0b
のようです。0b
ってどういう状態でしょうか。
どうやら、通常の起動時がこれのようですね。たしかに今当該のベースステーションは緑色LEDが点灯しています。
ベースステーションをスリープにする
ここまで来れば電源管理を行うのは簡単です。確認したハンドルに値を書き込むだけ。ちなみに電源管理は
00
: スリープ01
: 起動02
: スタンバイ
となっているようなので必要な値をかきこみます。
1 2 |
[D4:2C:B1:xx:xx:xx][LE]> char-write-req 0x12 00 Characteristic value was written successfully |
はい。これだけ。ベースステーションがスリープに落ちました!
なお、char-write-req
は相手先機器から返事をまちますが、char-write-cmd
で投げっぱなしにすることも可。
1コマンドで操作する
ここまでgatttoolsの対話型インタフェース内でコマンドを打ってきましたが、ハンドルと値がわかったのでコマンドを直接引数から指定して実行できます。
1 |
$ sudo gatttool -b D4:2C:B1:xx:xx:xx -t random --char-write-req -a 0x12 -n 01 |
はい。たったこれだけ。ベースステーションが起動しました。ただ時々失敗することもあるみたい。なんで……
モダンなAPIを使う
くりかえしますがgatttoolは廃止されたレガシーなAPIです。bluetoothctl
はroot権限なしでつかえるのにgattoolはrootいるんだもん、あれだよね。っていうことでそう、今のBlueZではGATTはbtgatt-client
でD-Bus経由で扱えるようです。sudo
不要です。
では試してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
btgatt-client -t random -d D4:2C:B1:xx:xx:xx Connecting to device... Done [GATT client]# Service Added - UUID: 00001800-0000-1000-8000-00805f9b34fb start: 0x0001 end: 0x0009 [GATT client]# Service Added - UUID: 00001801-0000-1000-8000-00805f9b34fb start: 0x000a end: 0x000d [GATT client]# Service Added - UUID: 00001523-1212-efde-1523-785feabcd124 start: 0x000e end: 0x0016 [GATT client]# Service Added - UUID: 00000000-0060-7990-5544-1cce81af42f0 start: 0x0017 end: 0x001a [GATT client]# Service Added - UUID: 0000fe59-0000-1000-8000-00805f9b34fb start: 0x001b end: 0x001e [GATT client]# Service Added - UUID: 0000180a-0000-1000-8000-00805f9b34fb start: 0x001f end: 0xffff [GATT client]# GATT discovery procedures complete [GATT client]# service - start: 0x0001, end: 0x0009, type: primary, uuid: 00001800-0000-1000-8000-00805f9b34fb charac - start: 0x0002, value: 0x0003, props: 0x02, ext_props: 0x0000, uuid: 00002a00-0000-1000-8000-00805f9b34fb charac - start: 0x0004, value: 0x0005, props: 0x02, ext_props: 0x0000, uuid: 00002a01-0000-1000-8000-00805f9b34fb charac - start: 0x0006, value: 0x0007, props: 0x02, ext_props: 0x0000, uuid: 00002a04-0000-1000-8000-00805f9b34fb charac - start: 0x0008, value: 0x0009, props: 0x02, ext_props: 0x0000, uuid: 00002aa6-0000-1000-8000-00805f9b34fb service - start: 0x000a, end: 0x000d, type: primary, uuid: 00001801-0000-1000-8000-00805f9b34fb charac - start: 0x000b, value: 0x000c, props: 0x20, ext_props: 0x0000, uuid: 00002a05-0000-1000-8000-00805f9b34fb descr - handle: 0x000d, uuid: 00002902-0000-1000-8000-00805f9b34fb service - start: 0x000e, end: 0x0016, type: primary, uuid: 00001523-1212-efde-1523-785feabcd124 charac - start: 0x000f, value: 0x0010, props: 0x08, ext_props: 0x0000, uuid: 00008421-1212-efde-1523-785feabcd124 charac - start: 0x0011, value: 0x0012, props: 0x1a, ext_props: 0x0000, uuid: 00001525-1212-efde-1523-785feabcd124 descr - handle: 0x0013, uuid: 00002902-0000-1000-8000-00805f9b34fb charac - start: 0x0014, value: 0x0015, props: 0x1a, ext_props: 0x0000, uuid: 00001524-1212-efde-1523-785feabcd124 descr - handle: 0x0016, uuid: 00002902-0000-1000-8000-00805f9b34fb service - start: 0x0017, end: 0x001a, type: primary, uuid: 00000000-0060-7990-5544-1cce81af42f0 charac - start: 0x0018, value: 0x0019, props: 0x02, ext_props: 0x0000, uuid: 00000010-0060-7990-5544-1cce81af42f0 descr - handle: 0x001a, uuid: 00002901-0000-1000-8000-00805f9b34fb service - start: 0x001b, end: 0x001e, type: primary, uuid: 0000fe59-0000-1000-8000-00805f9b34fb charac - start: 0x001c, value: 0x001d, props: 0x28, ext_props: 0x0000, uuid: 8ec90003-f315-4f60-9fb8-838830daea50 descr - handle: 0x001e, uuid: 00002902-0000-1000-8000-00805f9b34fb service - start: 0x001f, end: 0xffff, type: primary, uuid: 0000180a-0000-1000-8000-00805f9b34fb charac - start: 0x0020, value: 0x0021, props: 0x02, ext_props: 0x0000, uuid: 00002a29-0000-1000-8000-00805f9b34fb charac - start: 0x0022, value: 0x0023, props: 0x02, ext_props: 0x0000, uuid: 00002a24-0000-1000-8000-00805f9b34fb charac - start: 0x0024, value: 0x0025, props: 0x02, ext_props: 0x0000, uuid: 00002a25-0000-1000-8000-00805f9b34fb charac - start: 0x0026, value: 0x0027, props: 0x02, ext_props: 0x0000, uuid: 00002a27-0000-1000-8000-00805f9b34fb charac - start: 0x0028, value: 0x0029, props: 0x02, ext_props: 0x0000, uuid: 00002a26-0000-1000-8000-00805f9b34fb [GATT client]# |
お前勝手に全サービス表示してくれるんかい!
……ではスリープにしてみます。やることは一緒。
1 2 3 4 |
[GATT client]# write-value 0x12 00 [GATT client]# Write successful |
簡単!!!!!!おしまい!!!!
ついでなんでベースステーションの識別もしてみましょう。LEDが白色点滅するやつです。これには0x00
に適当なバイトを書き込むだけです。値はなんでも大丈夫な模様。
1 2 3 |
[GATT client]# write-value 0x10 35 [GATT client]# Write successful |
ベースステーションのLEDが点滅しました。識別したいときに使いましょう。
まとめ
ということでBlueZからGATTでベースステーションとおしゃべりをしました。BLEの内部実装はさておき、表にでてるインタフェースは結構単純になってるようで助かります。
本当はD-Busを叩いて操作するやつもしたかったものの一気に面倒になったりほしい部分がまだなかったりするようなこともあったのでパス。アドバタイズとかについてはほとんど触れなかったものの、ここまでわかれば色々な実装ができそうな気がして……こない?WindowsもAPIとかあるみたいだし。