OculusGoをROSと通信させる

# OculusGoでロボットを動かしたい
[ROS#](https://github.com/siemens/ros-sharp/wiki)というライブラリ・ツールを使うと、Unityから[ROS (Robot Operating System)](http://wiki.ros.org/Documentation)と通信することができます。
Unityが使えるならOculusGoでもできるのでは？と思って挑戦してみました。  
次の３つのデバイスを使用します。 
・OculusGo
・Unityを動かすWindows PC
・ROSを動かすLinux PC（Webカメラ付き）


## 1) UnityをセットアップしてOculusGoのサンプルを実行する
1. Unityをインストール  
 - OculusGoの開発環境はUnity 2017.4.1等が推奨のようですが、深く考えずに2018.1.7をインストールしてしまったのでこのまま進めます。  
https://developer.oculus.com/blog/everything-you-need-to-know-to-develop-for-oculus-go/  
https://developer.oculus.com/documentation/unity/latest/concepts/unity-req/  
<br/>
1. Android SDKとJDKをインストール  
 - OculusGoはAndroidベースなので、Android SDKとJDKが必要。  
 - Android SDKはAndroid Studioに付属しているので、以下のリンクからAndroid Studioダウンロード／インストール。  
https://developer.android.com/studio/  
 - JDKは以下のリンクからダウンロード／インストール。  
http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html  
<br/>
1. PlatformをAndroidに変更  
 - File > Build Settingsを開き、Androidを選択してOpen load pageをクリック。  
 - UnitySetup-Android-Support-for-Editor-2018.1.7f1.exeをダウンロードして実行。  
 - File > Build Settings > Switch PlatformをクリックしてPlatformを変更。  
<br/>
1. VRの設定  
 - Edit > Project Settings > Playerを選択。  
 - XR SettingsのVirtual Reality Supportedにチェック。  
 - Virtual Reality SDKsの欄にOculusを追加。  
<br/>
1. Oculus開発者に登録  
 - 以下のリンクからOrganizationを作成。  
https://dashboard.oculus.com/  
 - スマホのOculusアプリから、設定 > その他設定 > 開発者モードをON。  
USBケーブルをPCに接続してOculus Goを起動し、PCからのアクセスを許可。  
<br/>
1. サンプルをインポート  
 - OculusUtilities Unity packageをダウンロード。  
https://developer.oculus.com/downloads/unity/  
 - 解凍して、packageファイルをAssetsフォルダにドラッグ＆ドロップ。  
Assets > Oculus > VR > Scenesで、GearVrControllerTestをダブルクリック。  
<br/>
1. Buildのための設定  
 - アプリのパッケージ名を設定。  
Edit > Project Settings > Player > Other > Package Nameを、com.[CompanyName].[ProductName]の形で適当な組織名とプロダクト名に変更。  
 - APIレベルを設定。  
Edit > Project Settings > Player > Other Settings > Identification > Minimum API Level > API Level 25  
  - Scripting Runtime Versionを設定。  
Edit > Project Settings > Player > Other Settings > Configuration > Scripting Runtime Versionを.NET 4.x Equivalentに変更。  
（後で試したところ、デフォルトの.NET 3.5 EquivalentだとROS#で受信ができなかったため）  
 - ビルドするシーンを選択。  
File > Build Settings > Add Open Scenesをクリックし、現在のシーンをビルドの対象とする。  
<br/>
1. 実行  
 - OculusGoをUSBでPCに接続した状態で、File > Build Settings > Build&Runをクリックする。ビルド完了後にOculusGoを起動するとすぐに実行される。   
 - もう一度実行するには、ライブラリ > 提供元不明 > リストの最後あたりに、7.で設定したパッケージ名があるのでそれを選択する。  

## 2) ROS#を使ってROSと通信する  
1. ROS#用の新しいSceneを作る  
 - File > New Sceneで新しいSceneを作成。  
 - Assetsフォルダ内にScenesフォルダを作成し、そこに現在のSceneを保存。
 - HierarchyウィンドウからMain Cameraを削除。
 - ProjectウィンドウからAssets/OculusGo/VR/Prefabsを開き、OVRCameraRigをHierarchyウィンドウにドラッグ＆ドロップ。  
<br/>
1. ROS#をインポート  
 - 以下のリンクから、RosSharp.unitypackageをダウンロード。  
https://github.com/siemens/ros-sharp/releases
 - Assets > Import Package > Custom Packageから、先ほどダウンロードしたRosSharp.unitypackageを選択してインポートする。  
<br/>
1. RosConnectorの設定  
 - Hierarchyウィンドウで右クリック > Create EmptyでGameObjectを作成し、名前をRosConnectorに変更。  
 - RosConnectorのInspectorウインドウを開き、Assets/RosSharp/Scripts/RosCommunicationからRosConnectorをドラッグ＆ドロップ
 - RosBridgeServerUrlを設定。  
ROS側のPCのIPアドレス（例：192.168.0.100）とポート番号（デフォルト：9090）をあわせて、"ws://192.168.0.100:9090"のように入力。  
<br/>
1. 準備と確認
 - ROS側のPCの準備
     - 以下のコマンドでROS側のPCにRosBridge Serverをインストール。  
```$ sudo apt-get install ros-<rosdistro>-rosbridge-server```  
http://wiki.ros.org/rosbridge_suite  
     - 次のコマンドを入力し、RosBridge Serverを立ち上げる。  
```$ roslaunch rosbridge_server rosbridge_websocket.launch```  
 - Unityの確認
     - UnityのPCがROS側と同じネットワークにあることを確認。
     - 画面上部のPlayボタンをクリック。  
     - ROS側のPCのターミナルに```Client connected```と表示されたら接続できている。  
<br/>
1. OculusGoで実行  
 - 接続が上手くいかないと、OculusGoが入力を受け付けなくなって再起動が必要になる可能性があるので注意。  
 - OculusGoがROS側のPCと同じネットワークのWiFiに繋がっていることを確認。
 - ROS側のPCでRosBridgeServerを立ち上げる。
 - OculusGoをUnity側のPCとUSBで繋げた状態で、UnityのFile > Build&Runを実行。
 - ビルド完了後にOculusGoを起動し、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)に設定。  
 - ProjectウィンドウのAssets/RosSharp/Scripts/MessageHandlingからImageReceiverスクリプトを、ImageReceiverのInspectorウィンドウにドラッグ＆ドロップ。  
<br/>
1. RosConnectorの設定
 - RosConnectorのInspectorウィンドウを開き、Assets/RosSharp/Scripts/RosCommunicationからSubscriberをドラッグ＆ドロップ。
 - SubscriberのTopicを”/cv_camera/image_raw/compressed”に設定。  
 - SubscriberのMessageReceiverにImageReceiverオブジェクトをドラッグ＆ドロップ。  
<br/>
1. ROS側のPCの設定
 - Webカメラの映像をキャプチャして配信してくれるcv_cameraをインストール  
```$ sudo apt-get install ros-<rosdistro>-cv-camera```   
http://wiki.ros.org/cv_camera  
 - RosBridge Serverを立ち上げる。
 - 以下のコマンドでcv_cameraを立ち上げることで、Webカメラの映像が/cv_camera/image_raw/compressedのTopicとして配信される。  
```$ rosrun cv_camera cv_camera_node```  
<br/>
1. OculusGoで実行
 - 3)章と同様に、UnityのBuild&Runを実行し、ビルド完了後にOculusGoを起動。  
 - 下の図のようにWebカメラの映像が表示されたら成功。  
<img src="https://qiita-image-store.s3.amazonaws.com/0/248231/4a11ee4e-0562-9389-0b43-c0f3c86f04a7.jpeg" width=50%>

## 4) OculusGoの姿勢にあわせてカメラ画像を動かす  
1. 初期方向を表すTargetオブジェクトを作成  
 - Hierarchyウィンドウで右クリック > Create EmptyでGameObjectを作成し、名前をTargetに変更。  
 - InspectorウィンドウでPositionを(0,0,10)に設定。  
<br/>
1. 回転中心となるPlaneRotatorオブジェクトを作成  
 - Hierarchyウィンドウで右クリック > Create EmptyでGameObjectを作成し、名前をPlaneRotatorに変更。  
 - InspectorウィンドウでPositionを(0,0,0)に設定。  
 - HierarchyウィンドウでImageReceiverをPlaneRotatorの下層に配置。  
<br/>
1. スクリプトを作成
 - ProjectウィンドウでAssetsフォルダ内にScriptsフォルダを作成。  
 - Scriptsフォルダ内で右クリック > Create > C# Scriptでスクリプトを作成し、CamViewRotatorという名前に変更。  
 - CamViewRotatorを以下のように編集。  

    ```cs
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class CamViewRotator : MonoBehaviour {
    
    	public GameObject gameobject; // OculusGoの姿勢の参照用
    	public Transform target; // 初期方向の参照用
    
    	// Use this for initialization
    	void Start () {
    		
    	}
    	
    	// Update is called once per frame
    	void Update () {
    		// PlaneRotatorを初期方向に向かせる
    		transform.LookAt(target);
    		// OculusGoの姿勢にあわせてPlaneRotatorを回転させる
    		var trans = gameobject.transform;
    		transform.Rotate(trans.eulerAngles.x, trans.eulerAngles.y, 0.0f, Space.World); // 2軸のpan-tiltカメラ用
    		// transform.Rotate(trans.eulerAngles.x, trans.eulerAngles.y, trans.eulerAngles.z, Space.World); // 3軸のpan-tilt-rollカメラ用
    		// OculusGoの位置にあわせてPlaneRotatorを移動させる
    		transform.position = trans.position;
    	}
    }
    ```

1. PlaneRotatorの設定  
 - ProjectウィンドウのAssets/ScriptsからCamViewRotatorスクリプトを、PlaneRotatorのInspectorウィンドウにドラッグ＆ドロップ。
 - HierarchyウィンドウからOVRCameraRig/TrackingSpace/CenterEyeAnchorを、PlaneRotatorのInspectorウィンドウのGameobjectにドラッグ＆ドロップ。
 - HierarchyウィンドウからTargetを、PlaneRotatorのInspectorウィンドウのTargetにドラッグ＆ドロップ。
 - これでImageReceiverは常に画面中央に表示される。

## 5) OculusGoの姿勢を送信する  
1. PlaneRotatorの設定  
 - PlaneRotatorのInspectorウインドウを開き、Assets/RosSharp/Scripts/MessageHandlerからPoseProviderをドラッグ＆ドロップ。  
 - Frame IdにUnityと入力  
<br/>
1. RosConnectorの設定  
 - RosConnectorのInspectorウインドウを開き、Assets/RosSharp/Scripts/RosCommunicationからUnityTimePubliserをドラッグ＆ドロップ。  
 - RosConnectorのUnityTimePublisherのTopicに/cameraと入力し、Message ProviderにPlaneRotatorをドラッグ＆ドロップ。  
 - TimeStepを適当に5と設定。  
<br/>
1. 実行
 - ROS側のPCでRosBridge Serverを立ち上げる。
 - 3)章と同様にBuild&Runを実行。
 - ROS側のPCで新しいターミナルを開き、```$ rostopic echo /camera```を実行して下の図のような画面が表示されれば成功。  
 - orientationとして表示されるクォータニオンがOculusGoの姿勢（正確に言うとOculusGoにあわせて動くPlaneRotatorの姿勢）を表す。  
<img src="https://qiita-image-store.s3.amazonaws.com/0/248231/a0c9a7de-15f6-1410-1cde-1a42a6c5914a.png" width=80%>

# おまけ  
ROSを入れたRaspberryPi3を使って実際にロボットを動かしてみました。
クォータニオンをオイラー角に変換 → RosserialでArduinoに角度を送信 → Arduinoからサーボを制御  
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">OculusGoでロボットが動かせるようになったことをご報告いたします <a href="https://t.co/yYPzp9rfYb">pic.twitter.com/yYPzp9rfYb</a></p>&mdash; スプライタ@サクさんと梢さんの人 (@Spri_ta) <a href="https://twitter.com/Spri_ta/status/1016971100314451968?ref_src=twsrc%5Etfw">2018年7月11日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>