Ubuntu22.04上でROS2とQtCreatorを連携させる（Hello, World編 / トピック）

# はじめに

[前回記事](https://qiita.com/Yupopyoi/items/9bd819235352bb6dbdf0) ではROS Workspaceを作成するところまで行いました。

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

# 環境

Ubuntu 22.04
Qt Creator 13.0.0
ROS : Humble Hawksbill

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

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

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3607016/d380acad-5955-966d-476b-68eedf2f3f1b.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3607016/05c59856-c5f2-e31a-63db-d1f8765251fe.png)

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

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

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3607016/083dc2f1-9be0-c282-6d60-b4d5dd61f441.png)

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

:::note
ビルドシステムとしてcolconを使用しているためか、ここでDependenciesに依存パッケージを書き込んでもエラーが出てパッケージ作成できませんでした。
仕方がないので、手動でCMakeLists.txtとpackage.xmlに依存パッケージを記入したいと思います。
:::

:::note warn
pathも変更できるようになっていますが、srcディレクトリのままにします。
:::

:::note warn
「次へ」をクリックできない場合は、一度上の参照をクリックし、表示されるウィンドウを消すことで、クリックできるようになります。
:::

:::note alert
[2024/5/27 追記]
パッケージの名前にアルファベットの大文字を使ってはいけないようです。
**パッケージ名は小文字（＋アンダースコア＋数字）で書く**ようにしましょう。
```
rosidl_adapter.parser.InvalidResourceName: 'HelloWorld' is an invalid
package name.  It should have the pattern
'^(?!.*__)(?!.*_$)[a-z][a-z0-9_]*$'
```
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3607016/0b587aa7-07fd-0ef0-53da-c57c58c6d37d.png)

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3607016/17366257-609b-a921-5280-2056525700b3.png)

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

CMake については他の方がわかりやすく説明してくださっています。
CMake はビルドの自動化をしてくれるソフトウェアです。

https://qiita.com/sunrise_lover/items/589c4353d9547effc74b

## 3.1. CMakeLists.txt

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

**```find_package(<library_name> REQUIRED)``` は、今後も頻繁に使用するので覚えておくといいと思います。**

```CMake: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 と std_msgs を使用できるよう、追記します。

```xml: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>
```

-----

:::note

**[2024年7月10日更新]**

**さっさとロボットを動かしたい方はこのタイミングでこちらへ**

:::

https://qiita.com/Yupopyoi/items/5e5ae97091e7c692d58e

-----

# 4. トピックの実装

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

## 4.1. cppファイル作成

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3607016/31dbedec-de01-2846-82a8-f463a942f172.png)

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

:::note
cppファイルはsrcディレクトリ内に置きます。
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3607016/5fc97ae8-28c7-2244-1742-b21ef0f95146.png)

## 4.2. CMakeLists.txtの更新

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

```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})
```
<details><summary>簡単な説明</summary>

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}）にインストールする指示を行う。

</details>

:::note

CMakeListsでは関数を定義することもできます。
これを使用することで、cppファイルが多くなっても繰り返し同じような文を書く必要がなくなります。
```txt:CMakeLists.txt

function(config_executable target)
    add_executable(${target} src/${target}.cpp)
    ament_target_dependencies(${target} rclcpp std_msgs)
    install(TARGETS ${target} DESTINATION lib/${PROJECT_NAME})
endfunction()

config_executable(talker)
config_executable(listener)

```
:::

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

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

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3607016/9c164de2-2215-3f23-a3d3-768567d4f44f.png)

:::note
<details><summary>この段階で実行してエラーが出る場合の対処法</summary>

この段階で実行すると
```
エラー: /usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu/Scrt1.o: in function `_start':
```

のようなエラーが現れるかもしれません。これはmain関数が存在しないことに起因して起こるようです。

```cpp:talker.cpp
#include <rclcpp/rclcpp.hpp>

int main(int argc, char *argv[])
{
    std::cout << "Hello, World" << std::endl;
}
```

なんでもいいので、main 関数を用意してあげるとエラーが発生しなくなります。

</details>
:::

## 4.4. talker.cppの実装

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

```cpp: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;
}

```

<details><summary>コードざっくり解説</summary>

**クラス定義**
```cpp
class Talker : public rclcpp::Node
```

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

Nodeクラスのリファレンスは[ ここ ](https://docs.ros2.org/bouncy/api/rclcpp/classrclcpp_1_1_node.html)にあります。
<br>

**コンストラクタ**

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

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

**メッセージ設定**

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

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

**publish_Message関数オブジェクト**

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

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

**publisher設定**
```cpp
publish_ = create_publisher<std_msgs::msg::String>(topic_name, 10);
```

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

**タイマー設定**
```cpp
timer_ = create_wall_timer(std::chrono::milliseconds(100), publish_Message);
```

create_wall_timer関数もNodeクラスのメンバ関数です。第１引数が実行周期、第２引数が定期的に実行したい処理（関数オブジェクト）です。
<br>

**main関数**
```cpp
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() を使用します。

</details>

## 4.5. listener.cppの実装

```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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3607016/84906f99-5434-4f12-1597-d3db46661ddf.png)

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3607016/6c063b78-355f-8fa3-985c-a8481b3c8621.png)

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


## GUI制作編

https://qiita.com/Yupopyoi/items/04ca7eb7a87ba348c26d