ROS2のYAMLファイルを使ったパラメータ設定でパラメータ宣言をせずにパラメータを取得する

# TL;DR
一々パラメータの宣言せずに、yamlに書いたパラメータを`get_parameter`で取得したい！って場合。
- ノードのオプションを設定し、コンストラクタに渡す。
    - `rclcpp::NodeOptions`の変数を作り、以下を追加
    - `allow_undeclared_parameters(true)`
    - `automatically_declare_parameters_from_overrides(true)`
    - （`use_intra_process_comms(true)`などプロセス内通信を行う他のオプションも必要あれば追加しておく）
- コンストラクタ内で継承したノードに渡したオプションで初期化する。

```yaml:config.yaml
test_node:
  ros__parameters:
    param1: 100
    # 以下、必要に応じてパラメータが続く
```

```c++:main.cpp(ノードオプションをmain関数で渡す)
int main(int argc, char *argv[])
{
rclcpp::init(argc, argv);
// ノードオプションの設定
rclcpp::NodeOptions node_options;
node_options.allow_undeclared_parameters(true);
node_options.automatically_declare_parameters_from_overrides(true);
// node_options.use_intra_process_comms(true);

std::shared_ptr<TestNode> test_node = nullptr;
test_node = std::make_shared<TestNode>("test_topic", "test_node", node_options);
...
}
```

```c++:TestNode.cpp(コンストラクタ)
TestNode::TestNode(const std::string& topic_name, const std::string& node_name, const rclcpp::NodeOptions &options)
:Node(node_name, options)
{
// declareしなくても指定のパラメーターが取得できる
auto param1 = this->get_parameter("param1").as_string();
// 以下PublisherやSubscriberの設定が続く
}

```

```c++:main.cpp(実行ファイル内から取得する場合)
#include<TestNode.hpp>

std::shared_ptr<TestNode> test_node = nullptr;
test_node = std::make_shared<TestNode>("test_topic", "test_node");
// 上記同様declareしなくても指定のパラメーターが取得できる
auto param1 = test_node->get_parameter("param1").as_string();
// 以下executor等が続く
```

```bash:ros2実行
ros2 run test test_exec --ros-args --params-file config.yaml
```

# はじめに
ROS2では`ros2 run`実行時に`--ros-args --params-file`を付与することで、yamlファイルからパラメータを呼び出すことができ、非常に便利です。
以下の記事を参考になると思います。
[YAMLファイルによるROS2のパラメータ設定](https://qiita.com/NeK/items/15bf1e657d8d694592ed)

ただ、コンストラクタ内、もしくはノードのメンバ関数`declare_parameter("パラメータ名", 引数)`を使ってパラメータを宣言したあとに、`get_parameter("パラメータ名")`でパラメータを取得する必要が有ります。
## 例
```yaml:config.yaml
test_node:
  ros__parameters:
    param1: 100
    # 以下、必要に応じてパラメータが続く
```

```c++:TestNode（コンストラクタ内）
TestNode::TestNode(const std::string& topic_name, const std::string& node_name): Node(node_name)
{
this->declare_parameter("param1", "test")
auto param1 = this->get_parameter("param1").as_string();
// 以下PublisherやSubscriberの設定が続く
}
```

```c++:main.cpp（実行ファイル内）
#include<TestNode.hpp>

std::shared_ptr<TestNode> test_node = nullptr;
test_node = std::make_shared<TestNode>("test_topic", "test_node");
test_node->declare_parameter("param1", "test");
auto param1 = test_node->get_parameter("param1").as_string();
// 以下executor等が続く
```

`declare_parameter`を忘れてしまい、あれ、取得できない、、、なんてこともしばしば起こります。
（"param1" is not declared みたいなエラーが出てROS2実行できません）
パラメータが増えれば増えるほど、declareするパラメータも増えるので辛いです。

# Nodeのオプションを指定
Nodeのコンストラクタ内でオプションを渡すだけで、簡単にパラメータを宣言する必要が無くなります。
[プロセス内通信](https://docs.ros.org/en/foxy/Tutorials/Intra-Process-Communication.html)を利用している方はコンストラクタにおいて`rclcpp::NodeOptions().use_intra_process_comms(true)`でNodeのオプションを初期化しているのをお目にしているのではないでしょうか？そう、それです。
今回は以下のオプションを利用します。

- `allow_undeclared_parameters(true)`
- `automatically_declare_parameters_from_overrides(true)`

上記オプションのAPI Referenceは

- [allow_undeclared_parameters](https://docs.ros2.org/dashing/api/rclcpp/classrclcpp_1_1NodeOptions.html#a203b8059759eaa4fb498abc58b1cb33b)
- [automatically_declare_parameters_from_overrides](https://docs.ros2.org/dashing/api/rclcpp/classrclcpp_1_1NodeOptions.html#a094ceb7af7c9b358ec007a4b8e14d40d)

になります。
`allow_undeclared_parameters`で宣言していないパラメータの使用を許可し、もし、オーバーライドしたパラメータがあれば`automatically_declare_parameters_from_overrides`でそれも含めて暗黙的に宣言します。
Referenceにも書いてある通り、`allow_undeclared_parameters`だけだと、オーバーライドしたパラメータはノード上には現れません。
ただ、今回の例は`automatically_declare_parameters_from_overrides`が要らなさそうですね（今更ながら）。

具体的には、実行ファイルとなるmain関数でNodeを生成する際に以下のようにオプションを渡すだけでOKです。
（もちろん、Nodeのヘッダーファイルとソースファイルで引数を受け取れるようにしておく必要が有ります。）

```c++:main.cpp(ノードオプションをmain関数で渡す)
int main(int argc, char *argv[])
{
rclcpp::init(argc, argv);
// ノードオプションの設定
rclcpp::NodeOptions node_options;
node_options.allow_undeclared_parameters(true);
node_options.automatically_declare_parameters_from_overrides(true);
// node_options.use_intra_process_comms(true);

std::shared_ptr<TestNode> test_node = nullptr;
test_node = std::make_shared<TestNode>("test_topic", "test_node", node_options);
...
}
```

```c++:TestNode.cpp(コンストラクタ)
TestNode::TestNode(const std::string& topic_name, const std::string& node_name, const rclcpp::NodeOptions &options)
:Node(node_name, options)
// ↑初期化
{
// これでdeclareしなくても指定のパラメーターが取得できる
auto param1 = this->get_parameter("param1").as_string();
// 以下PublisherやSubscriberの設定が続く
}
```

```c++:main.cpp(実行ファイル内から取得する場合)
// ... Nodeの変数生成等
// 上記同様declareしなくても指定のパラメーターが取得できる
auto param1 = test_node->get_parameter("param1").as_string();
// 以下executor等が続く
```

## as_stringやas_intを付けたくない
パラメータによっては、stringだったりintだったり型が異なる場合が有ります。
パラメータ取得の際に毎回`as_string()`, `as_int()`等付けることは余計なミスが増える原因になります。
その場合は以下のようにテンプレートを用いましょう。

```c++:テンプレート
// テンプレート関数を実装
template <class T>
void SetParameter(const std::weak_ptr<rclcpp::Node>& node, std::string parameter_name, T& out)
{
  // std::weak_ptrでNodeを受け取り、lock()することでshared_ptrを得る
  if (auto node_handle = node.lock()){
    node_handle->get_parameter(parameter_name, out);
  } else {
    throw std::runtime_error("ERROR");
  }
}

int main(int argc, char *argv[])
{
rclcpp::init(argc, argv);
// ノードオプションの設定
rclcpp::NodeOptions node_options;
node_options.allow_undeclared_parameters(true);
node_options.automatically_declare_parameters_from_overrides(true);
// node_options.use_intra_process_comms(true);

std::shared_ptr<TestNode> test_node = nullptr;
test_node = std::make_shared<TestNode>("test_topic", "test_node", node_options);


// パラメータ用の変数
int width;
std::string save_dir;

SetParameter(test_node, "camera_width", width);
SetParameter(test_node, "save_dir_name", save_dir);
}

```


# 参考
- [YAMLファイルによるROS2のパラメータ設定](https://qiita.com/NeK/items/15bf1e657d8d694592ed)