Unity 初心者が ROS を利用するさいにハマったこと

#概要
Unity初心者がどうしても ROS を利用したくて試行錯誤してみました．
なんとか利用できるようになりましたが，ハマったことが沢山あったので，自分のためにも他の方のためにもまとめておこうと思います．

ちなみに，モチベーションとしては，以前，下記の記事で車を作ったのですが，その車を ROS で制御できるようにしたいといのが根底にあります．

* [unity で簡単な車を作ってみた](https://qiita.com/kanetugu2018/items/a6411ee86b2ed404031b)

※ゆくゆくは，[mROS](https://qiita.com/m_ksg/items/0060a8738e6a34a0f237), [athrill](https://qiita.com/kanetugu2018/items/1f2ef93c9e1fa7a29f97) を使って Unity 上の車を制御できるようにするのが最終目標です．

#一般的な方法
Unity から ROS 通信する際の一般解は，どうも 「ROS#(ros sharp)」のようです(多分)．
ソースは，下記サイトで一般公開されています．

* https://github.com/siemens/ros-sharp

ROS#の特徴は以下の通り(私見)．

* C# で実装されている(なので ROS#なのか？）
* Unity のスクリプトは C# なので，ROS#をそのまま利用できる(C#のアプリでも当然使える)
* ROS 通信は WebSocket ベースで実装されている
* TCPで直接実装されていないので Rosbridge と呼ばれるサーバーが必要になる
* さらに，直接ROS通信しないので，オーバーヘッドが気になる
* とはいえ，ROS# の ROS-API はわかりやすい(直感的に使えた)．

#参考にしたサイト
初心者なもので，下記サイト等を参考にさせて頂き，色々とお世話になりました(感謝)

* https://kato-robotics.hatenablog.com/entry/2018/10/30/170446
* https://kuwamai.hatenablog.com/entry/2018/11/28/200011
* https://qiita.com/Spritaro/items/5eb99b2fcdc26a3816ce

#環境
今回，私が実施した環境を以下の通りでした．

|環境|バージョン|
|---|---|
|Unityの動作環境|Windows 10 pro|
|Unity|Unity 2018.3.7f1|
|ROS|kinetic|
|ROSの動作環境|WSL|


#ハマったこと
ROS# の基本的な導入の流れは，先述のサイトでほぼカバーされているのですが，そのままやってもダメだった点を以下にまとめたいと思います．

* ROS# を Unity にインストールする方法がわからなかった
* ROS# で利用して良い API がわからなかった
* APIの正しい利用手順がわからなかった
* ROS# で実装したスクリプトをどうやって動かすのかわからなかった
* subscribe で購読トピックを ROSマスタに登録できたのに，なぜかコールバック関数が呼ばれなかった

##ROS# を Unity にインストールする方法がわからなかった
ROS#は，Unity の Asset Store でインストールできるそうなので，さっそく検索して download したのですが，Importできない問題に出くわしました・・．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244147/94138556-bf30-deee-51c3-75237e35af75.png)

上図の通り，「ウッ，Import ボタン押せない！」
どうも Unityバージョンが 2019.3.0 でしか利用できないような気がしたので，Unity 2019.3.0 をセットアップして，ROS#をインストールしました．ただ，Unity 2019.3.0，安定版ではないようで，頻繁に Unity がハングアップしてしまったので，Unity 2019.3.0 は諦めました．

しょうがないので，ROS# の公式サイトから Unity 2018 でも使えそうなバージョンを見繕ってダウンロード(RosSharp.unitypackage)し，Unity の「Assets/Import Package/Custom Package...」でインストールしました．

* https://github.com/siemens/ros-sharp/releases/tag/v1.3

## ROS# で利用して良い API がわからなかった
今回，ROS# の API を利用して，やりたかったことは以下の２点です．

* Unity 内の情報を文字列で publish し，rostopic echo で購読する
* rostopic pub でトピック出版(文字列)し，Unity 内の内部データを更新する

「上記を実現するために使うことができる API は何なのさ？」という問いから始まり，ググったり，ROS#の提供ライブラリの実装を読みながら，おおよそ以下なのだろうとわかってきました．

ベースは「RosSocket」で，ROS の基本的なAPIはここに全て揃っていました(実装はバイナリ提供のようです)．
API は，以下の通りで，ROSでおなじみの Advertise, Subscribe, Publish が揃っています．

```C#
namespace RosSharp.RosBridgeClient
{
    public class RosSocket
    {
        public IProtocol protocol;

        public RosSocket(IProtocol protocol);

        public string Advertise<T>(string topic) where T : Message;
        public string AdvertiseService<Tin, Tout>(string service, ServiceCallHandler<Tin, Tout> serviceCallHandler)
            where Tin : Message
            where Tout : Message;
        public string CallService<Tin, Tout>(string service, ServiceResponseHandler<Tout> serviceResponseHandler, Tin serviceArguments)
            where Tin : Message
            where Tout : Message;
        public void Close();
        public void Publish(string id, Message message);
        public string Subscribe<T>(string topic, SubscriptionHandler<T> subscriptionHandler, int throttle_rate = 0, int queue_length = 1, int fragment_size = int.MaxValue, string compression = "none") where T : Message;
        public void Unadvertise(string id);
        public void UnadvertiseService(string id);
        public void Unsubscribe(string id);
    }
}
```


## APIの正しい利用手順がわからなかった
上記の通り，使えそうなAPIはわかりましたが，どうやってそれを使えば良いのかわからない．
要するに，どうプログラミングするのかがさっぱりわかりませんでした．

そこで，「Assets/RosSharp/Scripts/RosCommunication」内のスクリプトを色々と読みながらわかってきたのは，以下の通りです．

* RosConnector.cs がベースとなっており，WebSocket ベースでの ROSの接続管理を担当している
* Publish したいときは，Publish.cs の Publish クラスを継承してトピック出版プログラムを作成する
* Subscribe したいときは，Subscribe.cs の Subscribe クラスを検証してトピック購読プログラムを作成する

## ROS# で実装したスクリプトをどうやって動かすのかわからなかった
なんとなく API の利用手順はわかりましたが，それを Unity 上でどうやってセットアップして動かすのかがよくわかりませんでした．

ただ，ここは参考にさせて頂いたサイトからスクリプトをドラッグ＆ドロップすればよいと書かれていましたので，その手順で実施したところ，下図のようなパラメータ設定をするだけで自動で動いてくれそうだということが直感的に見えてきました．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244147/02868303-e3d7-0093-aae3-e66cc10e09c1.png)

少し解説しますと，上図では，２つのスクリプトを配置しています．

１つ目は，RosConnector.cs です．
ドロップするとパラメータ設定が出来るようになっており，Ros Bridge Server Url に適切な値を設定すれば ROS通信できそうだなとわかりました．

２つ目は，自分で作った Subscriber 用のプログラムです．
ROS#内の既存サンプルプログラムを参考にさせて頂きながら，作成しました(下図参照)．

```C#
namespace RosSharp.RosBridgeClient
{
    [RequireComponent(typeof(RosConnector))]
    public class StringSubscriber : Subscriber<Messages.Standard.String>
    {
        [ColorUsage(false, true)] public Color color1;
        [ColorUsage(false, true)] public Color color2;
        private bool isMessageReceived = true;
        private bool ledState = false;

        protected override void Start()
        {
            GetComponent<Renderer>().material.EnableKeyword("_EMISSION");
            obj = GameObject.FindGameObjectWithTag("cube");
            base.Start();
        }
        private void Update()
        {
            if (isMessageReceived == false)
            {
                return;
            }
            if (ledState)
            {
                GetComponent<Renderer>().material.SetColor("_EmissionColor", color1 * 3);
            }
            else
            {
                GetComponent<Renderer>().material.SetColor("_EmissionColor", color2 * 3);
            }
        }

        protected override void ReceiveMessage(Messages.Standard.String msg)
        {
            isMessageReceived = true;
            //Debug.Log("ReceiveMessage : " + msg.data);
            string data = msg.data;
            string value = data.Split(':')[1].Trim();
            if (value.Equals("0"))
            {
                Debug.Log("OFF:" + value);
                ledState = false;
            }
            else
            {
                Debug.Log("ON:" + value);
                ledState = true;
            }
        }
    }
}
```
Subscriber実装上のポイントは，以下の通りです．

* Start() で，base.Start() を呼び出し，トピック購読宣言をします．
* この際，コールバック関数の登録は不要です．
* ReceiveMessage関数を実装しておけば，自動的にコールバック関数が登録されます．
* 親クラス(Subscribe)がうまいことやっていて，トピック購読データ受信時にコールバックされるという設計でした．
* 肝心のトピック名の定義はどこでやっているのか？ですが，
  * これは，Unityの Inspector 上で本スクリプトのパラメータ定義欄(Topic)で指定します．

## subscribe で購読トピックを ROSマスタに登録できたのに，なぜかコールバック関数が呼ばれなかった
ただ，この実装で動かしてみると，以下の現象に悩まされました．

* 購読トピックは rostopic list で見えるが，
* rostopic pub でトピック出版しても，コールバック関数(ReceiveMessage)が呼び出されない．

この問題，ROS#のバージョンが悪いのか，自分の実装方法がダメなのか，UnityとROS#との相性問題があったりするのか等，相当悩まされました．
しょうがないので，以下の仮説を考えてみました．

* ROS#は普及しているので，きっと大丈夫．
* 自分の実装もきっと大丈夫．（色々調べたけど，これ以外ないと思えた）
* 相性問題もあるかもしれないけれど，可能性は低いと思った．

そうなると，問題は 「rosbridge と ROS 通信との間にあるのではないか？」と思い当たりました．
※Unity/ROS#および自分の実装は問題ではない！

この仮説を検証するには，rosbridge と ROS 通信をやってみて，subscribe 出来るかどうかを確認すればよいだろうということになりました．

手っ取り早くやれるのは Node.js ベースで rosbridge を使ったROS通信です．

下記サイトのプログラムを多少改造して実験開始．

* https://naonaorange.hatenablog.com/entry/2019/02/12/202800

やってみたこと

* rosbridge を立ち上げた後，fuga.js(subscirber)を起動する
* rostopic pub でトピック出版する

結果，コールバック関数が呼ばれないという現象が再現されました！
仮説は当たっていたので，あとは，なぜ rosbridge はコールバック関数を呼び出さないのだろうか？とグーグル先生に聞くだけだです．

そして，見つけました！(Janneさんに感謝！）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244147/9d934da2-886a-9947-7879-faf54d0220d1.png)

※そして，とても悩まされたこの問題，忘れないように，twitter でメモ．
※https://twitter.com/esmtmori/status/1152492652966711296


#デモ
ようやくコールバック関数が動くようになったので，先週から頑張って作っていたLチカデモを拡張し，mROS を athrill 上で動かしながら Unity 上で Lチカするサンプルプログラムを作ってみました～．

![mROS_unity_LED.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244147/b7db7c97-8d08-940f-1f8b-6b4b2a04927f.gif)


#最後に
本記事では解説しておりませんでしたが，publishの方はうまくできました．
これでおおよその準備ができましたので，次は，車を制御してみます．
乞うご期待ください．