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


TL;DR

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

config.yaml
test_node:
  ros__parameters:
    param1: 100
    # 以下、必要に応じてパラメータが続く
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);
...
}
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の設定が続く
}

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等が続く
ros2実行
ros2 run test test_exec --ros-args --params-file config.yaml

はじめに

ROS2ではros2 run実行時に--ros-args --params-fileを付与することで、yamlファイルからパラメータを呼び出すことができ、非常に便利です。
以下の記事を参考になると思います。
YAMLファイルによるROS2のパラメータ設定

ただ、コンストラクタ内、もしくはノードのメンバ関数declare_parameter("パラメータ名", 引数)を使ってパラメータを宣言したあとに、get_parameter("パラメータ名")でパラメータを取得する必要が有ります。

config.yaml
test_node:
  ros__parameters:
    param1: 100
    # 以下、必要に応じてパラメータが続く
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の設定が続く
}
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のコンストラクタ内でオプションを渡すだけで、簡単にパラメータを宣言する必要が無くなります。
プロセス内通信を利用している方はコンストラクタにおいてrclcpp::NodeOptions().use_intra_process_comms(true)でNodeのオプションを初期化しているのをお目にしているのではないでしょうか?そう、それです。
今回は以下のオプションを利用します。

  • allow_undeclared_parameters(true)
  • automatically_declare_parameters_from_overrides(true)

上記オプションのAPI Referenceは

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

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

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

as_stringやas_intを付けたくない

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

テンプレート
// テンプレート関数を実装
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);
}

参考