LoginSignup
1
5

Meta Quest 3で ROS2 (ros2-for-unity)を使う

Last updated at Posted at 2024-02-27

以前MetaQuest2+ROS(ROS#)でラジコンのカメラ画像を表示+ラジコンを操縦するアプリを作りましたが、それをMetaQuest3+ROS2対応しました。

環境はこちらです。

  • ROS2 humble
  • Unity 2022.3.11.f1

(※)2024/3/6 事前準備とエラー対応を追記
(※)2024/4/8 ソースコードとビルドしたパッケージをGithubで公開し、Githubnへのリンクを追記(Link

ros2-for-unity

UnityでROS2を使うにはRobotecAIが開発したros2-for-unityとUnity公式が開発したUnity-Robotics-Hubがあります。

後者のUnity-Robotics-Hubは通信の際TCPに変換をするため通信が遅くなります。そのためros2-for-unityを使用しました。

詳しく解説している記事は多くあるのでそちらをご参照ください。例えばこちら。

ros2-for-unityをMetaQuest3で使う

ros2-for-unityはwindowsもしくはubuntu上のunityで動作します。一方MetaQuest3はAndroidなので対応が必要となります。
ありがたいことにros2-for-unityのAndroid対応をしている方がいるので、この方が公開しているソースコードををベースに開発しました。

ros2-for-unityのビルド

ビルド済みパッケージ

以下で説明するビルドエラーの修正を行ったソースコードと、ビルド済みのUnity PackageをGihubにアップしました。参考にしてください。

  • Unity Package: Link
  • ソースコード: Link

ビルドエラーの修正

基本的にはこの方が公開しているGithubリポジトリを使うのですが、Meta Quest 3のアプリ開発で必要となるUnity 2022.3.11.f1 + Android 10.0でビルドするとエラーが出るので修正する必要があります。

  • エラーメッセージ
gmake[2]: *** [src/cpp/CMakeFiles/fastrtps.dir/build.make:874: src/cpp/CMakeFiles/fastrtps.dir/fastdds/publisher/DataWriter.cpp.o] Error 1
gmake[2]: *** [src/cpp/CMakeFiles/fastrtps.dir/build.make:580: src/cpp/CMakeFiles/fastrtps.dir/rtps/reader/RTPSReader.cpp.o] Error 1
In file included from /app/3rdlib/ros2-for-unity/src/ros2cs/src/eProsima/Fast-DDS/src/cpp/rtps/writer/StatefulWriter.cpp:48:
In file included from /app/3rdlib/ros2-for-unity/src/ros2cs/src/eProsima/Fast-DDS/src/cpp/rtps/RTPSDomainImpl.hpp:23:
/app/3rdlib/ros2-for-unity/src/ros2cs/src/eProsima/Fast-DDS/thirdparty/filewatch/FileWatch.hpp:511:30: error: no viable overloaded '+='
                current_time += std::chrono::nanoseconds(result.st_mtim.tv_nsec);
  • 修正内容

Fast-DDS/thirdparty/filewatch/FileWatch.hppで、std::chrono::time_point<std::chrono::system_clock>のように変数を宣言している箇所があるので、ここすべて修正します。

(修正前)
std::chrono::time_point<std::chrono::system_clock> current_time

(修正後)
std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds > current_time;
  • 補足
    ros2-for-unityのビルドスクリプトを実行するとros2csがクローンされ、さらにros2csでいくつかのパッケージがクローンされますが、そのうちの1つがFast-DDSです。
    ros2-for-unityをクローンしたディレクトリから今回修正するファイルの相対パスはsrc/ros2cs/src/eProsima/Fast-DDS/thirdparty/filewatch/FileWatch.hppとなります。

ビルド手順

前提として、ROS2がビルドでき、. /opt/ros/humble/setup.bashを実行した環境である必要があります。

事前準備

もとのGithubリポジトリの手順にあるように事前にいくつかインストールする必要があります。
手順と同じ内容ですが、以下のようにソフトをインストールします。

# Install rmw and tests-msgs for your ROS2 distribution
sudo apt-get update
sudo apt install -y ros-humble-test-msgs
sudo apt install -y ros-humble-fastrtps ros-humble-rmw-fastrtps-cpp
sudo apt install -y ros-humble-cyclonedds ros-humble-rmw-cyclonedds-cpp

# Install vcstool package
curl -s https://packagecloud.io/install/repositories/dirk-thomas/vcstool/script.deb.sh | sudo bash
sudo apt-get update
sudo apt-get install -y python3-vcstool

# Install dotnet
sudo apt-get install -y apt-transport-https

sudo apt remove dotnet* aspnetcore* netstandard*
sudo rm /etc/apt/sources.list.d/microsoft-prod.list
sudo apt update
sudo apt install -y dotnet-sdk-6.0

ビルド

  1. ros2-for-unityのclone
    $ git clone https://github.com/Kotakku/ros2-for-unity.git
    
  2. 使用するパッケージのpull
    $ cd ros2-for-unity
    $ ./pull_repositories.sh
    
  3. 上で説明したビルドエラーを修正
  4. ビルド
    $ ./build.sh -p {NDKのパス} --clean-install --standalone
    
    (例)
    $ ./build.sh -p ~/2022.3.18f1/Editor/Data/PlaybackEngines/AndroidPlayer/NDK --clean-install --standalone
    
  5. unityパッケージのビルド(オプション)
    この手順は必須ではありませんが、やっていた方がunityにros2-for-unityを追加するのが簡単になります
    $ ./create_unity_package.sh -u {unity editorのパス}
    
    (例)
    $ ./create_unity_package.sh -u ~/Unity/2022.3.18f1/Editor/Unity
    
    成功するとinstall/unity_package/Ros2ForUnity.unitypackageが出力されます。

エラー対応

自分の環境では以下のビルドエラーがでました。環境によっては出ない人もいると思いますが、参考にしてください。

  • エラー1

    /usr/lib/python3/dist-packages/setuptools/command/install.py:34: SetuptoolsDeprecationWarning: setup.py install is deprecated. Use build and pip and other standards-based tools.
      warnings.warn(
    

    対応
    setuptoolsをインストール

    pip install setuptools==58.2.0
    
  • エラー2

    CMake Error at CMakeLists.txt:170 (find_package):
      By not providing "Findfoonathan_memory.cmake" in CMAKE_MODULE_PATH this
      project has asked CMake to find a package configuration file provided by
      "foonathan_memory", but CMake did not find one.
    
      Could not find a package configuration file provided by "foonathan_memory"
      with any of the following names:
    
        foonathan_memoryConfig.cmake
        foonathan_memory-config.cmake
    
      Add the installation prefix of "foonathan_memory" to CMAKE_PREFIX_PATH or
      set "foonathan_memory_DIR" to a directory containing one of the above
      files.  If "foonathan_memory" provides a separate development package or
      SDK, be sure it has been installed.
    

    対応
    根本解決方法はわかっていないのですが、CMakeLists.txtに以下のように追記をすることで回避できました

    ros2-for-unity/src/ros2cs/src/eProsima/foonathan_memory_vendor/CMakeLists.txt
    ...()...
    
    execute_process(COMMAND
        ${CMAKE_COMMAND}
        --find-package
        -DNAME=foonathan_memory
        -DCOMPILER_ID=$<IF:$<BOOL:${WIN32}>,MSVC,GNU>
        -DLANGUAGE=CXX
        -DMODE=EXIST
        -DCMAKE_FIND_DEBUG_MODE=ON
      ERROR_QUIET
      RESULT_VARIABLE _EXIT_CODE)
    
    SET(_EXIT_CODE 1) # <- 追加
    
    if(NOT _EXIT_CODE EQUAL 0)
    
    ...()...
    
  • 警告

        build/statistics_msgs/rosidl_generator_cs/statistics_msgs/msg/metrics_message_s.c:58:36: warning: address of 'ros_message->metrics_source.data' will always evaluate to 'true' [-Wpointer-bool-conversion]
      if (&ros_message->metrics_source.data)
    

    対策
    ビルドオプションを追加します

    ros2-for-unity/src/ros2cs/build_android.sh
    
    ...()...
    
    -DCMAKE_SHARED_LINKER_FLAGS="-Wl,-rpath,'\$ORIGIN',-rpath=.,--disable-new-dtags" \
    -DCMAKE_FIND_ROOT_PATH=${PWD}/install/ \
    -DCMAKE_HAVE_LIBC_PTHREAD=1 \
    --no-warn-unused-cli \
    -Wno-deprecated \
    -Wno-pointer-bool-conversion"
    
    ...(略)...
    
    

カスタムメッセージの追加

独自に実装したカスタムメッセージを追加することができます。方法は2つです。

  1. ros2_for_unity_custom_messages.reposにリポジトリを追加する
    カスタム決セージをgitリポジトリに上げている場合は、このファイルにリポジトリを追加することで自動的にリポジトリをクローン、ビルドしてくれます
    (例)
    ros2_for_unity_custom_messages.repos
    repositories:
      src/ros2cs/custom_messages/original_message:
        type: git
        url: https@github.com:xxxx/original_message.git
        version: main
    
    • 2行目のsrc/...: パッケージをクローンするディレクトリ
    • url: リポジトリのurl
    • version: ブランチ

Unityアプリのビルド

前提として、Meta Quest 3アプリの開発ができるよう設定をしておきます。

ros2-for-unityのインポート

エディタの上部のメニューでAsset -> ImportPackage -> CustomPackageを選択し、上の手順で作成したパッケージRos2ForUnity.unitypackageを選んでインポートします

アプリ実装

ここでは例として、カメラの画像(sensor_msgs/msg/CompressedImage)を受信し表示するアプリの実装をします。

  1. Hierarchyウィンドウで空のGameObjectを作成します。ここではRosManagerとします
  2. HierarchyウィンドウでPlaneを作成します。ここではCameraImageとします
  3. CameraImageのPosition, Rotation, Scaleを設定します。
    Position: (0, 0, 10)
    Rotation: (90, 0, 0)
    Scaleを設定します: (-1, -1, 0.75)
    
  4. Assetsフォルダ以下の任意の場所にCompressedImageSubscriber.csを作成します
  5. CompressedImageSubscriber.csの実装をします
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using ROS2;
    
    [RequireComponent(typeof(ROS2UnityComponent))]
    public class CompressedImageSubscriber : MonoBehaviour
    {
        public string topicName;
        public Renderer targetRenderer;
        private ROS2UnityComponent ros2Unity = null;
        private ROS2Node ros2Node = null;
        private ISubscription<sensor_msgs.msg.CompressedImage> image_sub = null;
        private Texture2D tex;
        static private byte[] recievedData = null;
        static object lockObject = new object();
    
        void Start()
        {
            ros2Unity = GetComponent<ROS2UnityComponent>();
    
            if (ros2Unity != null) {
                if (ros2Unity.Ok()) {
                    ros2Node = ros2Unity.CreateNode("ROS2UnityListenerNode");
                }
                tex = new Texture2D(1, 1);
            } else {
                Debug.Log("ros2Unity is null");
            }
        }
    
        void Update()
        {
            if (ros2Unity != null) {
                if (image_sub == null) {
                    Debug.Log("image_sub is null");
                } else {
                    Debug.Log("image_sub is not null");
                }
    
                if (ros2Unity.Ok() && image_sub == null) {
                    image_sub = ros2Node.CreateSubscription<sensor_msgs.msg.CompressedImage>(
                        topicName, CallbackCompressedImage);
                }
            } else {
                Debug.Log("ros2Unity is null");
            }
    
            lock (lockObject) {
                if (recievedData != null) {
                    tex.LoadImage(recievedData);
                    targetRenderer.material.mainTexture = tex;
                    tex.Apply();                
                } else {
                    Debug.Log("recievedData is null");
                }
            }
        }
    
        static void CallbackCompressedImage(sensor_msgs.msg.CompressedImage msg) {
            if (msg != null) {
                if (msg.Data != null) {
                    lock (lockObject) {
                        recievedData = new byte[msg.Data.Length];
                        Buffer.BlockCopy(msg.Data, 0, recievedData, 0, msg.Data.Length);
                    } 
                } else {
                    Debug.Log("msg.Data is null");
                }
            } else {
                Debug.Log("msg is null");
            }
        }
    }
    
    
    CreateSubscriptionで、データを受信したときに呼び出されるcallback関数を登録しています。
    このコールバック関数は別プロセスで動作するので、このコールバック関数内でテクスチャの設定をしようとするとエラーが発生していまいます。
    そのため、いったんバッファ(recievedData)に格納し、メインプロセスで実行されるUpdate関数内でテクスチャの設定をしています。
    注意点としてrecievedDataへのデータの書き込みと、データの読み込みが同時に行われることを防ぐため排他制御をする必要があります。
  6. InspectorウィンドウでRosManagerに、Ros2UnityComponentと上で作成したCompressedImageSubscriberを追加します。
  7. Inspectorウィンドウで、CompressedImageSubscriberのパラメータを設定します
    Topic Name: /image_raw/compressed
    Target Renderer: CameraImageをドラッグ&ドロップ

これをビルド・実行し、PCなどからCommpressdImageを出力すると、画像が表示されます。
表示例

任意のROS_DOMAIN_IDを使う方法

Meta Quest 3でも任意のROS_DOMAIN_IDを使うことができます。
そのために環境変数の設定とスクリプトの実行順を設定する必要があります。

  1. RosInitializer.csというスクリプトを追加し、以下の実装をします
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class RosInitializer : MonoBehaviour
    {
    
        public int rosDomainId = 1;
    
        // Start is called before the first frame update
        void Start()
        {
            // ROS_DOMAIN_ID設定
            Environment.SetEnvironmentVariable("ROS_DOMAIN_ID", rosDomainId.ToString());
            Debug.Log("current ROS_DOMAIN_ID:" + Environment.GetEnvironmentVariable("ROS_DOMAIN_ID") + "\n");
        }
    
        // Update is called once per frame
        void Update()
        {
            // Nothing
        }
    }
    
  2. Inspectorウィンドウ上でRosManagerRosInitializerを追加します
  3. Inspectorウィンドウ上でRosInitializerのパラメータを設定します
    Ros Domain Id: {使用したいROS_DOMAIN_IDの値}
  4. スクリプトの実行順を指定します
    ROS_DOMAIN_IDは、ROS2UnityComponentやCompressedImageSubscriberより先に設定する必要があります。
    何も指定しないと実行順がランダムとなるのでROS_DOMAIN_IDを設定する前にROSが立ち上がってしまい、意図したROS_DOMAIN_IDで送受信ができません。
    設定方法は以下のとおりです。
    1. 上部のメニューで、File -> Build Settings -> Player Settings -> Script Execution Orderを選択します
    2. 設定画面で以下の順でスクリプトが実装されるように設定します
      1. RosInitializer
      2. Ros2UnityComponent
      3. CompressedImageSubscriber
1
5
8

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
5