OculusQuestをROSと通信させる

## はじめに

以前、[OculusGoをROSと通信させ](https://qiita.com/Spritaro/items/5eb99b2fcdc26a3816ce)ていた者です。
最近Oculus Questを購入したので、さっそく使ってみたいと思います。

前回の執筆から1年以上経過してやり方もところどころ変わっているので、ソフトウェアのインストールからやり直します。今回使用するものは次の通りです。
- Unity 2018.4.6f1
- Android SDK, API Level 25
- Android SDK Build Tools
- Oculus Integration for Unity 1.39
- ROS# 1.5


## 1) Oculus Questの開発ができるようにセットアップする
1. Oculus Quest自体のセットアップ
  - [Device Setup - Oculus Quest](https://developer.oculus.com/documentation/quest/latest/concepts/mobile-device-setup-quest/)によると、以下の3つが必要です。
      - Oculus開発者に登録。
          - [こちら](https://dashboard.oculus.com/)からOrganizationを作成します。過去に作成したことがあれば不要です。
      - 開発者モードを有効化。
          - スマホのOculusアプリから、設定 > その他設定 > 開発者モードをON。
      - OculusGo ADB Driverのインストール
          - [OculusGo ADB Drivers](https://developer.oculus.com/downloads/package/oculus-go-adb-drivers/) のzipファイルをダウンロード。
          - zipファイルを解凍した後、中のinfファイルを右クリックしてインストールを選択。


1. Android開発用のセットアップ
  - Android SDK
      - Oculus Goと同様に、Oculus QuestはAndroidベースなのでAndroid SDKが必要です。まず、[こちら](https://developer.android.com/studio/index.html) からAndroid StudioをダウンロードしてPCにインストールします。
      - Android Studioを起動したら、画面右下のConfigureからSDK Managerを開いて、次のパッケージをインストールします。
          - Android SDK Platform, API level 25
          - Android SDK Build Tools
  - JDK (Java Development Kit)
      - Unity 2018.3.x以降にはOpenJDKベースのJDKが付属しているらしく、別途インストールする必要はないようです。


1. UnityをPCにインストール
  - Oculusの [Compatibility and version requirement](https://developer.oculus.com/documentation/unity/latest/concepts/unity-req/) によると、2019年8月現在、Unityのバージョンは2018.4 LTSなどが推奨のようです。
  - [Unity Hub](https://docs.unity3d.com/ja/current/Manual/GettingStartedInstallingHub.html) を使ってUnityをインストールします。Unity Hubのインストール画面右上のインストールボタンから2018.4 LTSの最新版を選択します。このときAndroid Build Supportモジュールを追加しておきます。インストールが完了するまでしばらく待ちます。
  - ![スクリーンショット 2019-08-15 22.49.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248231/1d8105b1-70d7-2ae7-84f0-f5e34e47dd78.png)


1. Unityプロジェクトの作成
  - Unity Hubのプロジェクト画面から新規作成ボタンを押してプロジェクトを作成します。適当なプロジェクト名と保存先を指定します。
  - ![スクリーンショット 2019-08-15 23.09.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248231/8fa25ec8-5252-a893-3dce-75fd1675eaec.png)
  - Unity本体が立ち上がったら、PlatformをAndroidに変更します。
      - File > Build Settings でBuild Settings画面を開く。
      - Platformの一覧からAndroidを選択。
      - 画面右下のSwitch PlatformボタンをクリックしてPlatformを変更。
      - ![スクリーンショット 2019-08-15 23.14.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248231/f194b769-f42c-9ffd-be6d-2a0b926f7133.png)


1. Oculus Unity Integrationのインポート
  - Asset Storeから [Oculus Integration for Unity](https://developer.oculus.com/downloads/package/unity-integration/) をインポートします。（かなり時間がかかるので気長に待ちます。）


1. Player Settingsの設定
  - Edit > Project Settings > Playerを選択してPlayer Settingsを表示します。
  - Other Settings
      - Package Nameを適当なものに変更します。com.[CompanyName].[ProductName]の形式です。
      - Minimum API LevelをAndroid 7.1 'Nougat' (API level 25)にします。
      - Scripting Runtime Versionが.NET 4.x Equivalentであることを確認し、Api Compatibility Levelを.NET 4.xに変更します。
  - XR Settings
      - Virtual Reality Supportedにチェックを入れます。
      - Virtual Reality SDKsの欄にOculusを追加します。


1. カメラの設定
  - HierarchyウィンドウからMain Cameraを削除します。
  - ProjectウィンドウからAssets/Oculus/VR/Prefabsを開き、OVRCameraRigをHierarchyウィンドウにドラッグ＆ドロップします。
  - OVRCameraRigのOVR Managerスクリプトにある、Target Devicesを```Ger VR Or Go```から```Quest```に変更します。


1. コントローラの設定
  - ProjectウィンドウからAssets/Oculus/VR/Prefabsを開き、OVRControllerPrefabをHierarchyウィンドウのRightControllerAnchorの下にドラッグ＆ドロップします。
  - LeftControllerAnchorの下にはOVRControllerPrefabをドラッグ＆ドロップします。こちらはInspectorウィンドウでControllerのタイプをL Tracked Remoteに変更します。
  - ![スクリーンショット 2019-08-16 00.10.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248231/2c6e73cd-7b21-360c-747d-3bf3886d339b.png)
  - 両手のコントローラを認識させるためには、メニューバーからOculus > Tools > Remove AndroidManifest.xml を実行した後、 Oculus > Tools > Create store-compatible AndroidManifest.xml を実行してAndroidManifest.xmlを更新する必要があります。
  - 更新したAndroidManifest.xmlは一部修正が必要です。ProjectウインドウのAssets/Plugins/AndroidからAndroidManifest.xmlをエディタで開き、<br>```<category android:name="android.intent.category.INFO"/>```<br>を<br>```<category android:name="android.intent.category.LAUNCHER"/>```<br>に書き換えます。

1. ビルドして実行
  - ビルドするシーンを選択。
       - File > Build Settingsを開き、Add Open Scenesをクリックして現在のシーンをビルドの対象とします。
  - USBケーブルでOculus QuestをPCと接続すると、PCからのアクセスを許可するかどうかのダイアログがOculus Quest内に表示されるので許可します。
  - Oculus QuestとPCを接続した状態で、Build And Runをクリックします。ビルド完了後にOculus Questを起動するとすぐに実行されます。
  - 再度実行するときは、Oculus Questの ライブラリ > 提供元不明のアプリ を開くと設定したPackage Nameがあるのでそれを選択します。


## 2) ROS#を使ってROSと通信する
1. ROS#のインポート
  - [こちらのリンク](https://github.com/siemens/ros-sharp/releases) から、RosSharp.unitypackageをダウンロードします。
  - Assets > Import Package > Custom Packageから、先ほどダウンロードしたRosSharp.unitypackageを選択してインポートします。
  - ちなみに、[Asset StoreからROS#をインポート](https://assetstore.unity.com/packages/tools/physics/ros-107085) することもできるらしいのですが、今のところUnity 2019.3.0以降にしか対応していないようです。

1. RosConnectorの設定
  - Hierarchyウィンドウで右クリック > Create EmptyでGameObjectを作成し、名前をRosConnectorに変更します。
  - ProjectウィンドウからAssets/RosSharp/Scripts/RosBridgeClient/RosCommunicationを開き、RosConnectorスクリプトを先ほど作ったRosConnectorオブジェクトにアタッチします。
  - アタッチしたRosConnectorスクリプトのRos Bridge Server Urlを設定します。ROS側のPCのIPアドレス（例：192.168.13.100）とポート番号（デフォルト：9090）をあわせて、```ws://192.168.13.100:9090```のように入力します。


1. ROS側のPCの準備
  - ROS#とROSの通信には [RosBridge Server](http://wiki.ros.org/rosbridge_suite) を使用します。
  - 以下のコマンドでROS側のPCにRosBridge Serverをインストールします。<br>
    ```$ sudo apt-get install ros-<rosdistro>-rosbridge-server```
  - 次のコマンドを入力し、RosBridge Serverを立ち上げます。<br>
    ```$ roslaunch rosbridge_server rosbridge_websocket.launch```


1. Oculus Questで実行
  - Oculus QuestがROS側のPCと同じネットワークのWiFiに繋がっていることを確認します。
  - ROS側のPCでRosBridgeServerが立ち上がっていることを確認します。
  - Oculus QuestをUnity側のPCとUSBで繋げた状態で、UnityのFile > Build&Runを実行します。
  - ビルド完了後にOculus Questを起動して、ROS側のPCのターミナルにClient connectedと表示されたら成功です。


## 3) メッセージの送受信
### カメラ画像の受信
1. ImageReceiverの作成
  - Hierarchyウィンドウで右クリック > 3D Object > Planeで平面を作成し、名前をImageReceiverに変更します。
  - InspectorウィンドウでPositionを(0, 0, 10)に、Rotationを(90, 0, 0)に、Scaleを(-1, -1, 0.75)に設定します。

1. RosConnectorの設定
  - ProjectウィンドウからAssets/RosSharp/Scripts/RosBridgeClient/RosCommunicationを開き、ImageSubscriberスクリプトをRosConnectorオブジェクトにアタッチします。
  - アタッチしたImageSubscriberスクリプトのTopicを```/camera/color/image_raw/compressed```に設定します。
  - ImageSubscriberスクリプトのMessageReceiverに、カメラ画像を映すImageReceiverオブジェクトをHierarchyウィンドウからドラッグ＆ドロップします。

1. ROS側のPCの設定
  - 普通のWebカメラを使ってもいいのですが、今回はIntel RealSense D435iを使用しています。<br>
    （RealSenseのセットアップ方法はここでは割愛します。）
  - RosBridge Serverを立ち上げます。<br>
    ```$ roslaunch rosbridge_server rosbridge_websocket.launch```
  - RealSenseノードを立ち上げます。<br>
    ```$ roslaunch realsense2_camera rs_camera.launch```

1. 実行
  - UnityのBuild&Runを実行し、ビルド完了後にOculus Questを起動します。<br>
  （起動後、ROS側のPCのターミナルにClient connectedと表示されるのにカメラ画像が受信できない場合、RosConnectorにImageSubscriberを2つアタッチしてビルドし直すと上手くいくことがあります。）


### 座標と姿勢の送信
1. RosConnectorの設定
  - ProjectウィンドウからAssets/RosSharp/Scripts/RosBridgeClient/RosCommunicationを開き、PoseStampedPublisherスクリプトをRosConnectorオブジェクトにアタッチします。
  - アタッチしたPoseStampedPublisherスクリプトのTopicに適当なトピック名を設定します。
  - PoseStampedPublisherスクリプトのPublished Transformに、座標と姿勢を取得するオブジェクトをHierarchyウィンドウからドラッグ＆ドロップします。ヘッドセットはCenterEyeAnchor、右コントローラはRightControllerAnchor、左コントローラはLeftControllerAnchorです。
  - ![スクリーンショット 2019-08-16 10.09.56_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248231/d8761d0c-3eb6-4566-4301-6e19a5a0f9c1.png)


1. 実行
  - ビルドしたアプリをOculus Questで実行した後、ROS側のPCで```$ rostopic echo <設定したトピック名>```を実行すると次のように表示されます。
<br>（Oculus Questは72FPSで動作しているのでトピックも72Hzで更新されるようですが、通信速度などによってはメッセージの送受信が間に合わなくなることがあるようです。）
  - ![スクリーンショット 2019-08-16 20.32.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248231/aea4916b-eaa2-7117-3281-07d5f2fc18d0.png)


### コントローラ入力の送信
1. RosConnectorの設定
  - ProjectウィンドウからAssets/RosSharp/Scripts/RosBridgeClient/RosCommunicationを開き、JoyPublisherスクリプトをRosConnectorオブジェクトにアタッチします。
  - アタッチしたJoyPublisherスクリプトのTopicに適当なトピック名を設定します。
  - ProjectウィンドウのAssets/RosSharp/Scripts/RosBridgeClient/MessageHandlingからJoyAxisReaderスクリプトとJoyButtonReaderスクリプトを必要な数だけRosConnectorオブジェクトにアタッチします。
  - アタッチしたJoyAxisReaderスクリプトのNameに読み取りたいAxisの名前を、JoyButtonReaderのNameに読み取りたいButtonの名前を設定します。設定できる名前は Edit > Project Settings > Input > Axes から確認できます。Typeが```Joystick Axis```であればAxis、```Key or Mouse Button```であればButtonです。各入力のレイアウトは [OVRInput](https://developer.oculus.com/documentation/unity/latest/concepts/unity-ovrinput/#touch-input-mapping) を参照してください。
  - ![スクリーンショット 2019-08-16 20.24.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248231/d59878e6-eb3a-63d9-fd12-eaecd164f48b.png)


1. 実行
  - ビルドしたアプリをOculusQuestで実行した後、ROS側のPCで```$ rostopic echo <設定したトピック名>```を実行すると次のように表示されます。
  - ![スクリーンショット 2019-08-16 20.38.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248231/ab0c5fa7-d0e2-6bcb-2f71-78fdea050559.png)