🤖

ROS2 for Unityで始めるUnityとROS2間の高速データ通信

2021/09/19に公開14

はじめに

こんにちは、ROS2で自律航行システム等を開発している片岡というものです。

https://github.com/OUXT-Polaris

競技で使う船は軽自動車くらいのサイズがあって試験場に持っていくだけで1日仕事、ということでシミュレータで日常の開発タスクや画像認識アルゴリズムの学習を行っていくことが非常に重要となります。
今回ros2-for-unityというネイティブでUnityとROS2を通信させるライブラリが公開されたのでそれの使い方を備忘録としてまとめておこうと思います。

https://github.com/RobotecAI/ros2-for-unity

今回は開発環境として以下の環境で動作を確認しました。

ubuntu: 20.04
Unity: 2021.1.18f1
ROS2: Foxy

なぜros2-for-unityを使いたいのか

過去にUnityとROS/ROS2をつなぐライブラリの実装例は以下のようなものがありました。

ros-sharp

ros-sharp

一番有名所のライブラリになります。
https://github.com/siemens/ros-sharp
rosbridge protocolにしたがって通信します。
websocket上でjson文字列による通信を行うため非常に大きな通信遅延とROSへのデータ変換の遅延が入ります。
画像、点群データを何個からも送ると数10ms~数100msの遅延が発生しトピックレートが大きく低下します。
最初の導入やWindowsでROS1と通信するアプリケーション作成には良いがヘビーユーズには適さないと言えます。

Unity-Robotics-Hub

https://github.com/Unity-Technologies/Unity-Robotics-Hub

Unity公式から発表されたライブラリです。
ROS1/ROS2と通信することができます。
プロトコルは独自ですがTCPベースということなので様々なプラットフォームで運用することが可能になるでしょう。

ただ、TCPでデータを送ってROS1/ROS2のデータ構造に変換するステップが入るのでそこに遅延が発生しますし、Server Endpointに一旦処理を集約するためそこが処理のボトルネックになる可能性が高いです。
ロボットのUIを作ったり、ロボットと連携して動くスマホアプリを作ったりするには適切なアーキテクチャだと思いますが、シミュレータとしてUnity環境を使うにはどうしても削れる遅延は削らなくてはなりません。
そのため採用の選択肢からは外れてしまいます。

ros2-for-unity

https://github.com/RobotecAI/ros2-for-unity

ros2-for-unityはRobotec.aiによって開発されたUnityとROS2間の通信を行うライブラリです。
ROS2 C#クライアントライブラリをネイティブライブラリとしてUnity Projectに取り込み、DDSのプロトコルで他のROS2アプリケーションと通信します。
OSはWindowsとUbuntuをサポートしています。
無駄な変換が一切挟まっていないため、これより早いプロセス間通信の方法は存在しないと言えます。
以上の理由からパフォーマンスを最優先として考えros2-for-unityをお試ししてみました。

ros2-for-unityのビルド

こちらはチュートリアルの通りにやればうまく行きました。
https://github.com/RobotecAI/ros2-for-unity/blob/master/README-UBUNTU.md
この通りにやるだけで以下に示す基本的なデータ型はあらかたビルドされていました。

actionlib_msgs
action_msgs
builtin_interfaces
diagnostic_msgs
geometry_msgs
lifecycle_msgs
nav_msgs
rcl_interfaces
ros2cs_common.dll.meta
ros2cs_core.dll.meta
rosgraph_msgs
sensor_msgs
shape_msgs
statistics_msgs
std_msgs
stereo_msgs
test_msgs
tf2_msgs
trajectory_msgs
unique_identifier_msgs
visualization_msgs

追加で欲しい場合はvcsに追記してビルドすれば良さそうです。

TalkerExampleでROS2 topicをpubする

公式のサンプルに日本語でコメントを追加しておきました。

// Copyright 2019-2021 Robotec.ai.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using UnityEngine;

namespace ROS2
{

/// <summary>
/// An example class provided for testing of basic ROS2 communication
/// </summary>
[RequireComponent(typeof(ROS2UnityComponent))] // ここでExecutor等が含まれるUnityコンポーネントが同じ階層にいる必要があるのを明示
public class ROS2TalkerExample : MonoBehaviour
{
    // Start is called before the first frame update
    private ROS2UnityComponent ros2Unity;
    private ROS2Node ros2Node;
    private IPublisher<std_msgs.msg.String> chatter_pub;
    private int i;

    void Start()
    {
        ros2Unity = GetComponent<ROS2UnityComponent>(); // ここでExecutor等が含まれるUnityコンポーネントを読み込む
    }

    void Update()
    {
        if (ros2Unity.Ok())
        {
            if (ros2Node == null)
            {
                ros2Node = ros2Unity.CreateNode("ROS2UnityTalkerNode"); // ここでノードの名前を指定
                chatter_pub = ros2Node.CreatePublisher<std_msgs.msg.String>("chatter");  // ここでノードのトピックを指定
            }

            i++;
            std_msgs.msg.String msg = new std_msgs.msg.String();
            msg.Data = "Unity ROS2 sending: hello " + i;
            chatter_pub.Publish(msg); // データをPublish
        }
    }
}

}  // namespace ROS2

このスクリプトを実行する前に、ROS2UnityComponentが以下の画像のようにインスペクター上で表示されていることを確認してください

Not Found

Sensorを作ってデータをPublishする

Unityの中にセンサーを作ってデータを取り出そうとする場合、どのセンサもほぼ定型的な処理を行うことになります。(計測フレームの判定、データ発行フレームの判定、タイムスタンプのセット、etc..)
そういった処理をまとめ上げたSensor型がros2-for-unityライブラリには含まれており、それを使用して手短にコードを書くことができます。
以下のセンサはPoseStampedを一定のレートで吐き出すセンサーのサンプルです。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using ROS2;
using geometry_msgs;

namespace ROS2ForUnityTest
{
[RequireComponent(typeof(ROS2UnityComponent))] // ここでExecutor等が含まれるUnityコンポーネントが同じ階層にいる必要があるのを明示
public class PoseSensor : ROS2.Sensor<geometry_msgs.msg.PoseStamped>
{
void Start()
{
    ros2Unity = GetComponent<ROS2UnityComponent>(); // ここでExecutor等が含まれるUnityコンポーネントを読み込む
}

protected override bool HasNewData() { return true; } // データ更新があればtrueを返す関数

protected override geometry_msgs.msg.PoseStamped AcquireValue() // 最新のセンサ値を返す関数
{
    pose_ = new geometry_msgs.msg.PoseStamped();
    return pose_;
}

protected override void OnUpdate() // Update() 処理で呼ばれる関数
{
    if (ros2Node == null)
    {
        ros2Node = ros2Unity.CreateNode("PoseSensor");
        CreateROSParticipants(ros2Unity, ros2Node, "/test");
    }
}

private geometry_msgs.msg.PoseStamped pose_;
private ROS2UnityComponent ros2Unity;
private ROS2Node ros2Node;
}
} // namespace ROS2ForUnityTest

このスクリプトを実行する前に、ROS2UnityComponentが以下の画像のようにインスペクター上で表示されていることを確認してください

Not Found

動作確認

Unity-Editor

Unity-Editor上で動作確認をするには、Unity Editor上の再生ボタンをクリックします。
ros2 topic echoするとデータがUnityの世界から発行されているのが確認できます。

ビルドして動作確認

File -> Build and Runを選択してバイナリをビルド、実行する。

Not Found
Not Found

上の画像のようにUnityの世界からデータが発行されているのが確認できます。

以下のリンクからビルドしたバイナリがダウンロードできるようにしておきました。
https://drive.google.com/file/d/1TbftSCeEbMg7mQgWDSJPu12ypkwx6vXD/view?usp=sharing

Ubuntu 20.04、Foxy環境で動作を確認しています。
お試しください。

Discussion

餡蜜餡蜜

質問です。
ros2-for-unityで独自メッセージをしたいのですが、TalkerExampleのコードをどのように書いたらいいかわかりません。独自メッセージはビルドして生成しました。tcpと書き方が違うので教えていただけると幸いです。返信の程よろしくお願いします。

hakuturu583hakuturu583

ビルドしたメッセージ方は共有ライブラリに入るのでそれをネイティブプラグインに読み込めばできるかと思われます。
この記事は書いてから時期が過ぎていますので一次資料(robotec.AIのリポジトリのドキュメント)をあたることを推奨します。

餡蜜餡蜜

返信していただきありがとうございます。
ネイティブプラグインというのはandroidということでしょうか?現在ビルドしたメッセージはrosmessgaesの中にあるのですが、ここからネイティブプラグインに読み込むためにはどのような操作をすれば良いかわからないです...
また、その操作の感じだとTalkerExampleは変更しなくても大丈夫でしょか
質問が多くて申し訳ないです

hakuturu583hakuturu583

Unityのネイティブプラグインとは、プラットフォーム固有の共有ライブラリを読み込む機能でありandroidに限定されません。
メッセージをビルドして出来上がったdllがネイティブプラグインです。
それをプロジェクトに追加してビルドすればネイティブプラグインを使ってる状態になります。

餡蜜餡蜜

返信していただきありがとうございます。
メッセージをビルドするというのはrosmessageを生成するという認識でよろしいでしょうか?
rosmessageを生成できればネイティブプラグインは使えている状態になるのでしょうか?
ros2-for-unityの環境構築をしたばかりであまり知識がなく、情報を書いているホームページも少ないためこのような形で情報収集をしています。お忙しいところ恐縮ですが、返信の程よろしくお願いします。

hakuturu583hakuturu583

お話を伺っているとおそらくUnityや静的リンク/動的リンク/ros2の仕組みへの理解を深めてからトライされた方が結果的に早くなるとは思います。
ros2 for unityはC#のros2クライアントです。
そしてそのクライアントが通信するためにはしリアライズされたデータを解凍しなくてはなりません。
そのために必要なのがメッセージのビルドです。
メッセージをビルドしてプラットフォーム固有の共有ライブラリを作成しそれをUnityプロジェクトに読み込むことでシリアライズされたデータをC#のデータ型に詰め直す必要があります。

餡蜜餡蜜

返信いただきありがとうございます。
まさに、仰るとおりです。私はros2の知識もunityの知識もあまりありません。なので自分なりに調べてわからなければ質問するといった言った感じで勉強しています。ご理解いただけるとありがたいです。
前回の質問で言われたdllファイルを見つけることができました。自分はTowIntsMsgという独自メッセージを追加したいです。記事に書いてあったVCSに追加する方法を教えていただけないでしょうか?
返信の程よろしくおねがいします。

餡蜜餡蜜

返信していただきありがとうございます
カスタムメッセージを参考に作業を勧めたところdllファイルを生成することができました。
しかし、独自メッセージの型をstd_msgs.msg.Stringの代わりに入れたところunityで以下のエラーが出てしまいました。どうやら独自メッセージの型が存在しないみたいです.
TwoIntsのディレクトリ内でint64型を宣言しました。
Assets/Ros2ForUnity/Scripts/ROS2TalkerExample.cs(28,48): error CS0426: The type name 'Int64' does not exist in the type 'TwoInts'
対処法等あれば教えていただけないでしょうか。
返信のほどよろしくお願いします

餡蜜餡蜜

返信していただきありがとうございます。
独自メッセージのファイル構成はmymessages.msg.TwoIntsと言う感じです。
TwoIntsディレクトリの中にはint64 Aとint64 Bを宣言しています。
あとはcmake.txtとxmlディレクトリです。
他に追加しなければならないディレクトリはサイトを見た感じないのですがもしあるなら教えていただきたいです。
毎回返信してくださいり、本当にありがとうございます。
返信のほどよろしくお願いします。

hakuturu583hakuturu583

やったのがかなり前なので記憶が定かではないですが、確かpackageのファイルをすべて入れた記憶はあります。
あと、自分の記事は古いですしros2 for unityにも更新が来てますので公式ドキュメントの参照を強く推奨します

餡蜜餡蜜

返信していただきありがとうございます
公式ドキュメントを見て作業しているのですが、うまくできないのでこちらで質問している感じです。
ご理解の程よろしくお願いします。
独自メッセージのpackageファイルの構成を知りたいのですが、どのような感じだったのか教えていただくことはできますでしょうか?
返信の程よろしくお願いします。

hakuturu583hakuturu583

数年前の作業を思い出せと言われても覚えてません。
公式ドキュメントを追ってわからなければそちらにissueを立てるなどしてください。