😸

Ubuntu22.04上でROS2とQtCreatorを連携させる(トピック通信)

2024/06/09に公開

はじめに

前回記事 ではROS Workspaceを作成するところまで行いました。

今回は talker.cpp と listener.cpp を実装し、ROS2のメッセージ通信手段の1つである、「トピック」を行うことを目標とします。

環境

OS : Ubuntu 22.04
Qt Creator 13.0.0
ROS : Humble Hawksbill

1. srcディレクトリを表示する

Qt Creator のデフォルト設定では空のディレクトリを非表示にしてしまうそうです。そのため、(特に何もしていなければ)src フォルダが表示されていないかと思います。

フィルターツリーの 「空のディレクトリを非表示にする」(Hide Empty Directories) のチェックを外すと、src ディレクトリが表示されるはずです。

image.png

image.png

2. ROS2パッケージを作成する

パッケージとはROS2コードを入れておく箱のようなものです。
Qt Creator を使用するとコマンドを使わずともパッケージ作成ができます。

ctrl+Nでファイルの新規作成が行えるので、そこから、ROS Package を選択します。

image.png

パッケージ名を入力します。
バージョン・ライセンス表記は好きにしてください。

image.png

すると、CMakeLists.txt と package.xml が追加されていることが確認できます。

3. 依存パッケージを記述する

3.1. CMakeLists.txt

必要なパッケージを追記します。
ここでは、C++を扱うのに必要な rclcpp (ROS Client Library for C++) および、標準メッセージ用パッケージである std_msgs を見つけてもらえるようにします。

CMakeLists.txt
# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)        # 追記
find_package(std_msgs REQUIRED)      # 追記
# uncomment the following section in order to fill in
# further dependencies manually.
# find_package(<dependency> REQUIRED)

3.2. package.xml

パッケージをビルド・実行するときに rclcpp を使用できるよう、追記します。

package.xml
  <buildtool_depend>ament_cmake</buildtool_depend>

 <!-- 追記ここから -->
 
  <build_depend>rclcpp</build_depend>
  <exec_depend>rclcpp</exec_depend>
  <build_depend>std_msgs</build_depend>
  <exec_depend>std_msgs</exec_depend>
  
  <!-- 追記ここまで -->

  <test_depend>ament_lint_auto</test_depend>
  <test_depend>ament_lint_common</test_depend>

4. トピックの実装

いよいよ本題です。
"Hello, World" を0.1秒に1回のペースで出力し続ける talker.cpp と、それを受け取る listener.cpp を作成していきます。

4.1. cppファイル作成

C++ Source File を選択します。

image.png

ファイル名とパスを指定します。
ここでは talker.cpp と listener.cpp を作成しました。

image.png

4.2. CMakeLists.txtの更新

CMakeLists.txtに次の内容を追記します。

CMakeLists.txt
add_executable(talker src/talker.cpp)
ament_target_dependencies(talker rclcpp std_msgs)
install(TARGETS talker DESTINATION lib/${PROJECT_NAME})

add_executable(listener src/listener.cpp)
ament_target_dependencies(listener rclcpp std_msgs)
install(TARGETS listener DESTINATION lib/${PROJECT_NAME})
簡単な説明

add_executable
実行可能ファイル(ここではtalker/listener)の定義。

talker/listener
実行可能ファイルの名前。src/talker.cpp あるいは src/listener.cpp をビルドして生成される。

src/talker.cpp
talkerという名前の実行可能ファイルのソースコードが格納されているファイルパス。

ament_target_dependencies(talker rclcpp std_msgs)
ターゲット(ここではtalker/listener)が依存するパッケージやライブラリを指定。

install
ビルドされたターゲット(ここではtalker/listener)を指定されたディレクトリ
(ここではlib/${PROJECT_NAME})にインストールする指示を行う。

4.3. ビルド&実行ファイル設定

左下のトンカチマークからビルドをします。とはいえ、これだけではまだ実行できません。

カスタム実行構成に実行ファイルを設定します。左のバーにある「プロジェクト」から「実行」を選択します。
「実行ファイル」に、先ほど CMakeLists で設定したディレクトリ内にある実行ファイルのパスを入力します。
「作業ディレクトリ」にはsrcディレクトリを指定します。

image.png

4.4. talker.cppの実装

実装に際しては、近藤 豊「ROS2ではじめよう 次世代ロボットプログラミング」(pp.74-76) を参考にしています。
エラーを吐いた部分の修正を行いました。

talker.cpp

#include <rclcpp/rclcpp.hpp>
#include <std_msgs/msg/string.hpp>

class Talker : public rclcpp::Node
{
    public:
        explicit Talker(const std::string &topic_name) : Node("talker")
        {
            message_ = std::make_shared<std_msgs::msg::String>();
            message_->data = "Hello, World";

            auto publish_Message =
                [this]() -> void
                {
                    RCLCPP_INFO(this->get_logger(),"%s", message_->data.c_str());
                    publish_->publish(*message_);
                };

            publish_ = create_publisher<std_msgs::msg::String>(topic_name, 10);
            timer_ = create_wall_timer(std::chrono::milliseconds(100), publish_Message);
        }

    private:
        std::shared_ptr<std_msgs::msg::String> message_;
        rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publish_;
        rclcpp::TimerBase::SharedPtr timer_;
};

int main(int argc, char *argv[])
{
    setvbuf(stdout, nullptr, _IONBF, BUFSIZ);
    rclcpp::init(argc, argv);

    auto node = std::make_shared<Talker>("chatter");
    rclcpp::spin(node);
    rclcpp::shutdown();

    return 0;
}

コードざっくり解説

クラス定義

class Talker : public rclcpp::Node

ノードを作成する際には rclcpp::Node クラスを継承します。

Nodeクラスのリファレンスは ここ にあります。

コンストラクタ

explicit Talker(const std::string &topic_name) : Node("talker")

コンストラクタの定義です。Node クラス(親クラス)のコンストラクタに "talker" という文字列を渡して初期化しています。

メッセージ設定

message_ = std::make_shared<std_msgs::msg::String>();
message_->data = "Hello, World";

make_shared 関数を使って、共有ポインタ (スマートポインタの一種) を作成します。
その後、"Hello, World" という文字列を代入します。message_ はポインタなのでアロー演算子を使用します。

publish_Message関数オブジェクト

auto publish_Message =
        [this]() -> void
        {
            RCLCPP_INFO(this->get_logger(),"%s", message_->data.c_str());
            publish_->publish(*message_);
        };

ラムダ式を使用しています。
RCLCPP_INFO() はログ表示のための関数で、これを書くと QtCreator のアプリケーション出力にログが表示されるようになります。

publisher設定

publish_ = create_publisher<std_msgs::msg::String>(topic_name, 10);

create_publisher 関数は Node クラスのメンバ関数です。第2引数は過去のメッセージ履歴をどれだけ保存するかを指定しています。第2引数の省略はできません。(以前はできた?)

タイマー設定

timer_ = create_wall_timer(std::chrono::milliseconds(100), publish_Message);

create_wall_timer 関数も Node クラスのメンバ関数です。第1引数が実行周期、第2引数が定期的に実行したい処理(関数オブジェクト)です。

main関数

int main(int argc, char *argv[])
{
    setvbuf(stdout, nullptr, _IONBF, BUFSIZ);
    rclcpp::init(argc, argv);

    auto node = std::make_shared<Talker>("chatter");
    rclcpp::spin(node);
    rclcpp::shutdown();

    return 0;
}

main 関数です。
setvbuf(stdout, nullptr, _IONBF, BUFSIZ) では標準出力のバッファリングを無効化し、即座に出力するよう設定します。
実行するときは rclcpp::spin() を使用します。

4.5. listener.cppの実装

listener.cpp

#include <rclcpp/rclcpp.hpp>
#include <std_msgs/msg/string.hpp>

class Listener : public rclcpp::Node
{
    public:
        explicit Listener(const std::string &topic_name) : Node("listener")
        {
            auto callback =
                [this](const std_msgs::msg::String::SharedPtr message) -> void
                {
                    RCLCPP_INFO(this->get_logger(),"%s", message->data.c_str());
                };

            subscription_ = create_subscription<std_msgs::msg::String>(topic_name, 10, callback);
        };

    private:
        rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
};

int main(int argc, char *argv[])
{
    setvbuf(stdout, nullptr, _IONBF, BUFSIZ);
    rclcpp::init(argc, argv);

    auto node = std::make_shared<Listener>("chatter");
    rclcpp::spin(node);
    rclcpp::shutdown();

    return 0;
}

talker.cpp と大きく変わっているわけではないので解説は省略します。

4.6. 実行

talker.cpp と listener.cpp をどちらも実行します。

image.png

すると、Listener が正しくデータ受信できていることが分かります。

image.png

これで、ひとまず「トピック」の実装まで終わりました。

次回

次回は Qt Creator で GUI を作ります。

Discussion