ROS2におけるコンポーネントの作成

【[ROS2関係トップページへ](https://qiita.com/NeK/items/7ac0f4ec10d51dbca084)】
【前:[ROS2の共有ライブラリを使用したプログラムの作成]】
【次:[ROS2のコンポーネントの利用](https://qiita.com/NeK/items/72cc4fe44e563533b67b)】

[ROS2の共有ライブラリを使用したプログラムの作成]:https://qiita.com/NeK/items/dfeb32b7762864641e42

共有ライブラリに更に手を加えてコンポーネントを作成する．
[ROS2における共有ライブラリ](https://qiita.com/NeK/items/48c55bd473091d41cc75)で作成したものを例にコマンド類の説明をする．

* パッケージ名
    * minimal_comp
* 共有ライブラリで使用できる機能・クラスを収めたソースファイル
    * minimal_comp_node1.hppと.cpp
        * クラス名:MinimalCompNode1
* パッケージ共通のnamespace
    * minimal_comp

# なぜにコンポーネント？

* 共有ライブラリとしても使える
    * ひと手間加えるだけで共有ライブラリとしてもコンポーネントとしても使える．
* ノード単体で起動可能である
    * ターゲットを作成しなくてもよい

特に2点目について説明する．
[ROS2の共有ライブラリを使用したプログラムの作成](https://qiita.com/NeK/items/dfeb32b7762864641e42)の[共有ライブラリを使用したプログラム](https://qiita.com/NeK/items/dfeb32b7762864641e42#%E5%85%B1%E6%9C%89%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%97%E3%81%9F%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0)にあるプログラムを再掲する．

```c++:minimal_comp_user_main.cpp
#include <rclcpp/rclcpp.hpp>
#include "minimal_comp/minimal_comp_node1.hpp"

int main(int argc, char * argv[]){
  rclcpp::init(argc,argv);
  rclcpp::spin(std::make_shared<minimal_comp::MinimalCompNode1>());
  rclcpp::shutdown();
  return 0;
}
```

このように共有ライブラリを単に起動するだけのターゲットを考えると，どんな共有ライブラリでも同じプログラムになるけど一々作成しなければならず面倒である．
こんな起動の仕方ならノードを直接起動しても良くない？→それコンポーネント，のイメージである(個人的見解)．

# コンポーネントの作り方
## dependencies
依存関係として<font color="Red">rclcpp_componentsを指定</font>する．
例えば以下のようになる．
コンポーネントはrclcpp::Nodeから継承するので，当然rclcppも必要．

```bash
$ ros2 pkg create minimal_comp --build-type ament_cmake --dependencies rclcpp rclcpp_components
```

パッケージ作成時にdependenciesを付けない場合，以下を手動で書き込む．

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

```text:CMakeLists.txt
find_package(rclcpp REQUIRED)
find_package(rclcpp_components REQUIRED)
```

## CMakeLists.txt
以下は関係する部分を抜き出したものとなる．詳しくは[ROS2における共有ライブラリ](https://qiita.com/NeK/items/48c55bd473091d41cc75)の[CMakeLists.txt](https://qiita.com/NeK/items/48c55bd473091d41cc75#cmakeliststxt)を参照のこと．

```text:CMakeLists.txt
add_library(minimal_comp_node SHARED
  src/minimal_comp_node1.cpp
)
rclcpp_components_register_nodes(minimal_comp_node "minimal_comp::MinimalCompNode1")
target_compile_definitions(minimal_comp_node
  PRIVATE "MY_LIBRARY_BUILDING_LIBRARY"
)
target_compile_options(minimal_comp_node PUBLIC -Wall)
ament_target_dependencies(minimal_comp_node
  rclcpp
  rclcpp_components
)
```

### 説明
重要となるのは，4行目と11行目(下から2行目)である．
4行目:rclcpp_components_register_nodesによりノードを登録．
11行目:依存関係があるので，ament_target_dependenciesにrclcpp_componentsを追加．

rclcpp_components_register_nodesの使い方は以下の通り．

```
rclcpp_components_register_nodes([target名] "namespace::クラス名")
```

## ソースファイル
ノードの実装をしている.cppファイル(例だとminimal_comp_node1.cpp)に以下を追記する．

* \#include "rclcpp_components/register_node_macro.hpp"
* RCLCPP_COMPONENTS_REGISTER_NODE(namespace::クラス名)
    * 一番下，namespaceの定義外に以下を追加
    * 例の場合以下のようになる
        * RCLCPP_COMPONENTS_REGISTER_NODE(minimal_comp::MinimalCompNode1)

### <font color="Red">注意点</font>
コンポーネントにするノードのコンストラクタは引数としてrclcpp::NodeOptionsの一つしか持てない．
よって[ROS2における共有ライブラリ](https://qiita.com/NeK/items/48c55bd473091d41cc75)の[共有ライブラリ化する二つのクラス](https://qiita.com/NeK/items/dfeb32b7762864641e42#%E5%85%B1%E6%9C%89%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%97%E3%81%9F%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0)のような以下のクラスだとエラーが起きる．

```c++:minimal_comp_node1.hpp
#include <rclcpp/rclcpp.hpp>
#include "minimal_comp/visibility.h" // 同一ディレクトリなら#include "visibility.h"でもok

namespace minimal_comp {

class MinimalCompNode1 : public rclcpp::Node{
public:
  MINIMAL_COMP_PUBLIC
  MinimalCompNode1(
    const std::string &name_space="",
    const rclcpp::NodeOptions& options=rclcpp::NodeOptions()
  );
};

}
```

#### 解決方法
解決方法は簡単で引数がrclcpp::NodeOptionsの一つとなるコンストラクタを作ってやればよい．
以下の二つ目のコンストラクタで，name_spaceにデフォルト引数を与えないように注意．
これで，name_spaceを引数にとれる共有ライブラリとしてもコンポーネントとしても使用可となる．

```c++:minimal_comp_node1.hpp
#include <rclcpp/rclcpp.hpp>
#include "minimal_comp/visibility.h" // 同一ディレクトリなら#include "visibility.h"でもok

namespace minimal_comp {

class MinimalCompNode1 : public rclcpp::Node{
public:
  MINIMAL_COMP_PUBLIC
  MinimalCompNode1(
    const rclcpp::NodeOptions& options=rclcpp::NodeOptions()
  );
  MINIMAL_COMP_PUBLIC
  MinimalCompNode1(
    const std::string& name_space,
    const rclcpp::NodeOptions& options=rclcpp::NodeOptions()
  );
};

}
```

これを使ってコンポーネント用に作り替えると以下のようになる．
2行目と22行目がコンポーネント登録となる．

```c++:minimal_comp_node1.cpp
#include <rclcpp/rclcpp.hpp>
#include "rclcpp_components/register_node_macro.hpp"
#include "minimal_comp/minimal_comp_node1.hpp"

namespace minimal_comp {

MinimalCompNode1::MinimalCompNode1(
  const rclcpp::NodeOptions& options
): MinimalCompNode1("", options)
{}

MinimalCompNode1::MinimalCompNode1(
  const std::string& name_space,
  const rclcpp::NodeOptions& options
): Node("minimal_comp1", name_space, options)
{
  RCLCPP_INFO(this->get_logger(),"minimal comp 1 test");
}

}

RCLCPP_COMPONENTS_REGISTER_NODE(minimal_comp::MinimalCompNode1)
```

# 参考
- [公式Q&A:ROS2 diff between node and component](http://answers.ros.org/question/313001/ros2-diff-between-node-and-component/)
- [teratail](https://teratail.com/questions/38812)
    - curlで特定のファイルをDL
- [curlコマンド 使い方メモ](https://qiita.com/yasuhiroki/items/a569d3371a66e365316f)
- [sed, awk, grepの使い分け](https://qiita.com/lca367/items/e174ce6985a1e7f3c441)