ROS2の最小構成subscriber:初級 -ROS1 style-

【[ROS2関係トップページへ](https://qiita.com/NeK/items/7ac0f4ec10d51dbca084)】
【[ROS2レクチャー:初級 -ROS1 style-](https://qiita.com/NeK/items/74e4bb9d1530e22a63fc)】

【前:[ROS2のpublisher/subscriber概要]】
【次:[ROS2における時間管理・Rate系とTimer系]】

[ROS2のpublisher/subscriber概要]:https://qiita.com/NeK/items/bb21b9e24f89f3182d05
[ROS2における時間管理・Rate系とTimer系]:https://qiita.com/NeK/items/bcf518f6dd79f970bb8e

[公式のAPI]:http://docs.ros2.org/foxy/api/rclcpp/index.html


ROS風nodeによるsubscriberプログラムを作成する．
ここで作成するプログラムは以下の2種類．

- ROS風nodeによるsubscriber．callback関数を使用．
- ROS風nodeによるsubscriber．lambda関数を使用．

また，ここではpublisherは作成せず，コマンドでメッセージを送信してsubscriberが受信する様子を観察する．

# 準備

```Bash:terminal
$ cd ~/ros2_studies_ws/
$ ros2 pkg create minimal_subscriber_ros1_like --dependencies rclcpp example_interfaces
```

# callback関数を使用したsubscriber
作成物:

- src/callback_main.cpp

## プログラム

```c++:callback_main.cpp
#include <rclcpp/rclcpp.hpp>
#include <rclcpp/qos.hpp>
#include <example_interfaces/msg/string.hpp>

rclcpp::Node::SharedPtr node = nullptr;

void topic_callback(const example_interfaces::msg::String::SharedPtr msg){
  RCLCPP_INFO(node->get_logger(), "I heard: %s", msg->data.c_str());
}

int main(int argc, char * argv[]){
  rclcpp::init(argc, argv);

  node=rclcpp::Node::make_shared("ros1_like_subscriber_test");
  auto subscription = node->create_subscription<example_interfaces::msg::String>(
    "topic_test",
    rclcpp::QoS(10),
    std::bind(topic_callback, std::placeholders::_1)
  );
  rclcpp::spin(node);
  rclcpp::shutdown();

  subscription = nullptr;
  node=nullptr;
  return 0;
}
```

### 概要
メッセージを受信したらcallback関数(topic_callback)を呼んで処理を任せる．
メッセージの種類はとりあえずぱっと使えるexample_interfaceで，その中のstring型([example_interfacesのmsgのstring](https://github.com/ros2/example_interfaces/blob/master/msg/String.msg))を使用．
subscriberのcallback関数はメッセージのポインタを受け取る．

### 説明
main関数とcallback関数(topic_callback)の両方でnodeを使用するので5行目のようにglobal変数にしてヌルポインタを代入しておく．
7～9行目がcallback関数．今回はメッセージを出力するだけのもの．
11行目からmain関数で，14行目でnodeを作成し15行目でそのnodeをsubscriberとして使用するための設定を行っている．

- create_subscription関数で「subscriberとして設定」
    - [公式のAPI]のSubscriptionの項参照
- subscriberとして(publisherから)受け取るメッセージの種類は<>内で設定
    - example_interfaces::msg::String
- 関数の第一引数(16行目)がトピック名の指定
    - topic_test
        - subscriberがトピックを作るのではなく，publisherが作るトピックの名前を指定
- 関数の第二引数(17行目)はQuality of Serviceの設定
    - rclcpp::QoS(n)で「historyをnほど持つ」という意味になるので，適当にnを設定
    - rclcpp::QoSの設定自体は様々あるみたいだが，ここではhistoryに関するもののみで「とりあえず」いい？
- 関数の第三引数(18行目)がメッセージを受け取った時の処理・callback関数を指定
    - 関数名とその関数に渡す引数を指定するためにstd::bindを使用
    - 参考:[【C++】std::bindの使い方](https://qiita.com/tkyaji/items/1aa7bb01658e848d3ef4)
- 設定をポインタ変数subscriptionで保存
    - subscriptionはautoで型類推しているが，本来はSubscription<example_interfaces::msg::String>型のスマートポインタ

<details><summary>\<rclcpp/qos.hpp\>について</summary>[公式APIのQOSの項目](http://docs.ros2.org/foxy/api/rclcpp/classrclcpp_1_1QoS.html)では\<qos.hpp\>となっているが，/opt/ros/foxy/include以下を見ると\<rclcpp/qos.hpp\>が正しそうに見える．APIの記述が変わるか，プログラムの記述を変えなくてはいけなくなるか分からないのでメモ</details>

20行目で，設定の終わったnodeを実行．

string型について

- 3行目のようにexample_interfaces/msg/string.hppをインクルード．
    - string.hppはファイル名
- 使用する場合には，7行目や15行目のようにexample_interfaces::msg::Stringと指定
    - Stringはクラス名
- メッセージはSharedPtr，UniquePtr，WeakPtrの使用が可
- [ros2/example_interfaces](https://github.com/ros2/example_interfaces/blob/master/msg/String.msg)を見るとわかるが，example_interfacesのStringクラスはstring型の変数dataのみを持つ．文字列として出力するために8行目のようにc_str()でC言語の文字列に変換している．

<details>
<summary>global変数，ホントウはだめ絶対</summary>C言語の場合にはstaticを使用してファイルスコープにすべき．C++では無名名前空間(namespace{})を使用すべきである．
</details>

# lambda関数を使用したROS風subscriber

- ~/ros2_studies_ws/src/minimal_subscriber_ros1_like/src/lambda_main.cpp

## プログラム

```c++:lambda_main.cpp
#include <rclcpp/rclcpp.hpp>
#include <rclcpp/qos.hpp>
#include <example_interfaces/msg/string.hpp>

int main(int argc, char * argv[]){
  rclcpp::init(argc, argv);

  node=rclcpp::Node::make_shared("ros1_like_subscriber_test");
  auto subscription = node->create_subscription<example_interfaces::msg::String>(
    "topic_test",
    rclcpp::QoS(10),
    [](const example_interfaces::msg::String::SharedPtr msg) ->void {
      RCLCPP_INFO(node->get_logger(), "I heard: %s", msg->data.c_str());
    }
  );
  rclcpp::spin(node);
  rclcpp::shutdown();

  return 0;
}
```

### 概要
ROS1風subscriberのcallback関数の代わりにlambda関数を用いたバージョン．
参考:[cpprefjpのラムダ式](https://cpprefjp.github.io/lang/cpp11/lambda_expressions.html)，[C++11 ラムダ式、std::function](https://qiita.com/rokuta96/items/13e69bdab0eacfa33940)，[本の虫: lambda 完全解説](https://cpplover.blogspot.com/2009/11/lambda.html)

### 説明
12～14行目:返り値がvoidで引数がconst example_interfaces::msg::String::SharedPtr msgのlambda関数．



# package.xmlとCMakeLists.txt
## package.xml
以下は重要な部分のみを抜粋．

```xml:package.xml
<package format="3">
  <depend>rclcpp</depend>
  <depend>example_interfaces</depend>
```

### 説明
rclcppに加え，example_interfacesを使用している．

## CMakeLists.txt
以下は重要な部分のみを抜粋．

```CMakeLists.txt
find_package(rclcpp REQUIRED)
find_package(example_interfaces REQUIRED)

add_executable(callback_subscriber_test
  src/callback_main.cpp
)
ament_target_dependencies(callback_subscriber_test
  rclcpp
  example_interfaces
)

add_executable(lambda_subscriber_test
  src/lambda_main.cpp
)
ament_target_dependencies(lambda_subscriber_test
  rclcpp
  example_interfaces
)

install(TARGETS
  callback_subscriber_test
  lambda_subscriber_test
  DESTINATION lib/${PROJECT_NAME}
)
```

rclcppに加え，example_interfacesを使用しているので2行目のように指定．
今回2種類のターゲットを作成する．そのため，add_executableおよびament_target_dependencies2種類ある．
またinstallのところに"ターゲット"も2種類となり，全てをlib/${PROJECT_NAME}にインストールするよう設定してある．

# ビルド・実行
## ビルド

```Bash:terminal
$ cd ~/ros2_studies_ws/
$ colcon build --symlink-install --packages-up-to minimal_subscriber_ros1_like
$ . install/local_setup.bash
```

colcon build --symlink-installのみの場合，ワークスペース以下全てのパッケージをコンパイル・ビルドするので時間がかかる．
--packages-up-to [パッケージ名]とすることで必要なパッケージおよび依存関係のあるパッケージのみコンパイル・ビルドできて楽．複数のパッケージを選択したい場合はスペース区切りで指定．

## 実行
subscriber(メッセージ受信者)とpublisher(今回はコマンドでメッセージを発信)のため，二つのterminalを起動させる．

```Bash:terminal1
$ cd ~/ros2_studies_ws/
$ . install/local_setup.bash
$ ros2 run minimal_subscriber_ros1_like [ターゲット名]
```

CMakeLists.txtにあるとおり，ターゲット名は以下の通り．全部やってみよう．

- callback_subscriber_test
- lambda_subscriber_test

```Bash:termina2
$ cd ~/ros2_studies_ws/
$ . install/local_setup.bash
$ ros2 topic pub /topic_test example_interfaces/String '{data: hello}'
```

下記のコマンドでデータを発信("pub"lication)している．詳しくは[ROS2の紹介 by gbiggs](https://gbiggs.github.io/rosjp_ros2_intro/ros2_tools.html#topic%E3%82%B5%E3%83%96%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89)．

- ros2 topic pub [トピック名] [データの型] [YAML形式で指定したデータ]

### 補足

```Bash:terminal
$ ros2 node list # ノード一覧が表示される
$ ros2 interface list # メッセージ一覧が表示される
$ ros2 interface show example_interfaces/msg/String # example_interfaces/msg/Stringの構造が表示される
$ ros2 topic list # トピック一覧が表示される
$ ros2 topic pub /topic_test std_msgs/String '{data: hello}'
```

詳しくは[Introspection with command line tools](https://index.ros.org/doc/ros2/Tutorials/Introspection-with-command-line-tools/)もしくは[ROS2コマンド一覧](https://qiita.com/NeK/items/33d62c61fc1a8c0c233e#ros2%E7%8A%B6%E6%85%8B%E7%A2%BA%E8%AA%8D)を参照．

【前:[ROS2のpublisher/subscriber概要]】
【次:[ROS2における時間管理・Rate系とTimer系]】