Help us understand the problem. What is going on with this article?

ROS2の非同期parameterクライアントについて

メモ:未整理の情報

ここではparameterに関するプログラムを作成する.
特にparameter clientとして非同期に取得・設定を行うAsyncParameterClientと同期的に行うSyncParameterClientを紹介する.

基本的には,宣言・値の設定・取得となり,それぞれ以下の関数となる.詳しくは以下の通り.

  • 宣言
    • declare_parameter
      • パラメータの宣言
      • dashing diademataから宣言なしでは値の設定・取得ができなくなった.これにより「パラメータの名前を打ち間違っちゃって似た名前のものが複数できる」などがなくなった.
    • undeclare_parameter
      • パラメータの無効化
    • has_parameter
      • パラメータの存在確認
  • 値の設定
    • set_parameters
  • 値の取得
    • get_parameter
    • get_parameters

2019/6/13現在,公式のAPIには無いようである.
公式のRoadmapに詳しく書いてある.
もしくは公式githubのrcl_interfaces

この他,parameter serviceや参照しているparameterが変更された場合に処理を行うcallback関数の機能,パラメータの権限に関するあれやこれやなどあるが,future worksで.

準備

terminal
$ cd ~/ros2_studies_ws/
$ ros2 pkg create minimal_parameter_class

ROS風 parameter

  • src/ros1_like_parameter_async_user_main.cpp
    • param_holderに接続し,値を参照したり設定したりするnode
  • src/ros1_like_parameter_sync_user_main.cpp
    • param_holderに接続し,値を参照したり設定したりするnode

プログラム : src/ros1_like_parameter_holder_main.cpp

src/ros1_like_parameter_holder_main.cpp
#include <rclcpp/rclcpp.hpp>
#include <chrono>

int main(int argc, char * argv[]){
  using namespace std::chrono_literals;
  rclcpp::init(argc, argv);
  auto node = rclcpp::Node::make_shared("param_holder");

  auto param1 = node->declare_parameter("foo",0);
  auto param2 = node->declare_parameter("bar", "ok");
  auto results = node->set_parameters({
    rclcpp::Parameter("foo",2),
    rclcpp::Parameter("bar","hello")
  });
  rclcpp::spin(node);
  rclcpp::shutdown();
  return 0;
}

概要

パラメータを保持するnodeとして作成.
nodeを作り,保持するパラメータを準備し,spinでnodeを実行したままにする.
このnode自身がパラメータを定義し,保持・管理する(nodeがserviceとなっている).よって,declare_parameterやget_parameter,setparametersなどはnodeが行っている(後述するclient側ではnodeがget,setしていないことに注意).またwait_for_serviceもしていない(service元のnodeが自分なので接続待ちをしない).

説明

7行目:nodeの設定.名前をparam_holderとする.
9~14行目:設定するparameterの宣言.名前だけでも良いし,デフォルト値を設定してもよい.
11~14行目:設定するparameterの準備.
15行目:parameterを持つnodeの実行.

Parameter

set_parameters関数の中でParameterを使用している.また公式のAPIにも記述があるが補足.

パラメータはキー(要素名?)と値の対にて表される.辞書型の変数.ここで,ROS2ではParameterクラスとParameterValueクラスが存在し,両方ともパラメータを表すのに使えそう.公式のAPIによるとParameterValueはパラメータを保持するためのクラスで,Parameterも同じ.ただParameterクラスはget, setに関する関数テンプレートを使っていて任意のパラメータを扱えるよう.

プログラム : src/ros1_like_parameter_async_user_main.cpp

src/ros1_like_parameter_async_user_main.cpp
#include <rclcpp/rclcpp.hpp>
#include <chrono>

int main(int argc, char * argv[]){
  using namespace std::chrono_literals;
  rclcpp::init(argc, argv);
  auto node = rclcpp::Node::make_shared("param_async_user");
  auto param_client = std::make_shared<rclcpp::AsyncParametersClient>(node, "param_holder");
  std::stringstream ss;

  while(!param_client->wait_for_service(1s)){
    if(!rclcpp::ok()){
      RCLCPP_ERROR(node->get_logger(), "Interrupted while waiting for the service.");
      return 1;
    }
    RCLCPP_INFO(node->get_logger(), "waiting for service...");
  }

  auto parameters_future = param_client->get_parameters({"foo", "bar"});
  if(rclcpp::spin_until_future_complete(node, parameters_future) != rclcpp::executor::FutureReturnCode::SUCCESS){
    RCLCPP_ERROR(node->get_logger(), "Failed to get param list.");
    return 1;
  }
  for(auto &param : parameters_future.get()){
    ss << "\nParameter name:" << param.get_name();
    ss << "\nParameter type:" << param.get_type_name();
    ss << "\nParameter value:" << param.value_to_string();
  }
  RCLCPP_INFO(node->get_logger(), ss.str().c_str());
  ss.str("");
  ss.clear(std::stringstream::goodbit);

  auto list_future = param_client->list_parameters({"foo","bar"},10);
  if(rclcpp::spin_until_future_complete(node, list_future) != rclcpp::executor::FutureReturnCode::SUCCESS){
    RCLCPP_ERROR(node->get_logger(), "Failed to get param list.");
    return 1;
  }
  for(auto &param_name : list_future.get().names){
    ss << "\n" << param_name;
  }
  for(auto &prefix : list_future.get().prefixes){
    ss << "\n" <<  prefix;
  }
  RCLCPP_INFO(node->get_logger(), ss.str().c_str());
  ss.str("");
  ss.clear(std::stringstream::goodbit);

  auto results_future = param_client->set_parameters({
    rclcpp::Parameter("foo",3),
    rclcpp::Parameter("bar","welcome")
    //rclcpp::Parameter("bar",10) // OK:別種のものを代入することは可能
    //rclcpp::Parameter("hoge","hige") // NG:パラメータを増やすことは無理
  });
  if(rclcpp::spin_until_future_complete(node, results_future) != rclcpp::executor::FutureReturnCode::SUCCESS){
    RCLCPP_ERROR(node->get_logger(), "Failed to get param list.");
    return 1;
  }
  for(auto &result : results_future.get()){
    if(!result.successful){
      RCLCPP_ERROR(node->get_logger(), "Failed to set parameter: %s", result.reason.c_str());
    }
  }

  parameters_future = param_client->get_parameters({"foo", "bar"});
  if(rclcpp::spin_until_future_complete(node, parameters_future) != rclcpp::executor::FutureReturnCode::SUCCESS){
    RCLCPP_ERROR(node->get_logger(), "Failed to get param list.");
    return 1;
  }
  for(auto &param : parameters_future.get()){
    ss << "\nParameter name:" << param.get_name();
    ss << "\nParameter type:" << param.get_type_name();
    ss << "\nParameter value:" << param.value_to_string();
  }
  RCLCPP_INFO(node->get_logger(), ss.str().c_str());
  ss.str("");
  ss.clear(std::stringstream::goodbit);

  rclcpp::shutdown();
  return 0;
}

概要

param_holderに接続し,そのnodeが持っているパラメータを参照・変更・また参照するプログラム.
パラメータを取得・変更する場合,パラメータを保持しているnodeと接続しなければいけない.そのため,8行目のようにparameter clientとしての設定を行っている.引数は二つであり,一つ目がAsyncParametersClientの機能を持たせるnodeへのポインタ,二つ目がparameterのデータを保持しているparameter nodeの名前(serviceとなるnodeの名前)である.
また,今回はAsync(非同期)型となっているので,参照・変更を有効にするときにspin_until_future_completeを使用している.このspinによって返ってくるのはFutureオブジェクトなので,処理結果はget関数で行っている.

説明

8行目:AsyncParametersClientとして設定.二つ目の引数で参照するparameter nodeの名前を渡している.
19行目:取得するparameterの設定.キーで指定.
20~23行目:spinによってparameter取得を実行.できなかったら(rclcpp::executor::FutureReturnCode::SUCCESSでなかったら)エラー処理となっている.
24~28行目:spinによって返ってくるのはFutureオブジェクト.よってparameterの取得結果(処理結果)はget関数で得る.複数のparameterを取得しているのでfor文で回している.
33~46行目:parameterのlist.詳細情報?33行目の二つ目の引数10は深さで,対象parameterの名前を深さ10まで取得するらしい.
48~62行目:paramter設定.値を変えることは可能(49,50行目).また値の種類を超えて変えることも可能(51行目).しかし新しいキーを設定することはできない模様(エラーにはならない).
64~76行目:parameter取得.

プログラム : src/ros1_like_parameter_sync_user_main.cpp

src/ros1_like_parameter_sync_user_main.cpp
#include <rclcpp/rclcpp.hpp>
#include <chrono>

int main(int argc, char * argv[]){
  using namespace std::chrono_literals;
  rclcpp::init(argc, argv);
  auto node = rclcpp::Node::make_shared("param_sync_user");
  auto param_client = std::make_shared<rclcpp::SyncParametersClient>(node, "param_holder");
  std::stringstream ss;

  while(!param_client->wait_for_service(1s)){
    if(!rclcpp::ok()){
      RCLCPP_ERROR(node->get_logger(), "Interrupted while waiting for the service.");
      return 1;
    }
    RCLCPP_INFO(node->get_logger(), "waiting for service...");
  }

  auto parameters_get_results = param_client->get_parameters({"foo", "bar"});
  for(auto &param : parameters_get_results){
    ss << "\nParameter name:" << param.get_name();
    ss << "\nParameter type:" << param.get_type_name();
    ss << "\nParameter value:" << param.value_to_string();
  }
  RCLCPP_INFO(node->get_logger(), ss.str().c_str());
  ss.str("");
  ss.clear(std::stringstream::goodbit);

  auto list_results = param_client->list_parameters({"foo","bar"},10);
  for(auto &param_name : list_results.names){
    ss << "\n" << param_name;
  }
  for(auto &prefix : list_results.prefixes){
    ss << "\n" <<  prefix;
  }
  RCLCPP_INFO(node->get_logger(), ss.str().c_str());
  ss.str("");
  ss.clear(std::stringstream::goodbit);

  auto parameter_set_results = param_client->set_parameters({
    rclcpp::Parameter("foo",3),
    rclcpp::Parameter("bar","welcome")
    //rclcpp::Parameter("bar",10) // OK:別種のものを代入することは可能
//    rclcpp::Parameter("hoge","hige") // NG:パラメータを増やしても無効?
  });
  for(auto &result : parameter_set_results){
    if(!result.successful){
      RCLCPP_ERROR(node->get_logger(), "Failed to set parameter: %s", result.reason.c_str());
    }
  }

  parameters_get_results = param_client->get_parameters({"foo", "bar"});
  for(auto &param : parameters_get_results){
    ss << "\nParameter name:" << param.get_name();
    ss << "\nParameter type:" << param.get_type_name();
    ss << "\nParameter value:" << param.value_to_string();
  }
  RCLCPP_INFO(node->get_logger(), ss.str().c_str());
  ss.str("");
  ss.clear(std::stringstream::goodbit);

  rclcpp::shutdown();
  return 0;
}

概要

Sync(同期)型のプログラム.Async型と異なり,spin関数を用いなくても,get_parameters, set_parametersなどを実行したらすぐに取得・設定が行われる.そのためクラス化したnodeの場合にはSync型を使用することになる(コンストラクタの中でパラメータの取得・設定を行うためにはAsyncでは無理っぽい).
また(Async型と同様に)get_parameter(get_parameters)で得られた返り値は型や名前など色々な情報を含む.パラメータの値そのものを得るためには,param.value_to_string()などの関数を使用する.代表的なものは以下の通り.その他は公式のAPIのrclcpp::Parameterを参照.

  • param.as_bool()
  • param.as_int()
  • param.as_double()
  • param.as_string()

またAsync型との違いは以下のとおり

  • spin_until_future_completeがなく,get_parameters, set_parametersがすぐに実行される
  • 結果がFutureオブジェクトでなく,処理結果そのままとなる.そのためAsyncでは返り値.get()で処理結果を得ていたが,Syncでは返り値をそのまま使用している(19,20行目,29,30行目,40,41行目,52,53行目).

package.xmlとCMakeLists.txt

<package format="3">
  <build_depend>rclcpp></build_depend>
  <test_depend>rclcpp></test_depend>
  <exec_depend>rclcpp></exec_depend>
CMakeLists.txt
find_package(rclcpp REQUIRED)

add_executable(minimal_parameter_holder_test
  src/minimal_parameter_holder_main.cpp
  src/minimal_parameter_holder.cpp
)
ament_target_dependencies(minimal_parameter_holder_test
  rclcpp
)

add_executable(ros1_like_param_async_user_test
  src/ros1_like_parameter_async_user_main.cpp
)
ament_target_dependencies(ros1_like_param_async_user_test
  rclcpp
)

add_executable(ros1_like_param_sync_user_test
  src/ros1_like_parameter_sync_user_main.cpp
)
ament_target_dependencies(ros1_like_param_sync_user_test
  rclcpp
)

install(TARGETS
  minimal_parameter_holder_test
  ros1_like_param_async_user_test
  ros1_like_param_sync_user_test
  DESTINATION lib/${PROJECT_NAME}
)

ビルド・実行

ビルド

terminal
$ cd ~/ros2_studies_ws/
$ colcon build --symlink-install --packages-select minimal_parameter
$ . install/setup.bash

実行

param_holderとuser用のterminalを起動.
param_holder用はROS1でもクラス化したものでも結果は変わらないので,ここではROS1風を実行.
またuser用はAsyncでもSyncでも結果は変わらないので,ここではAsyncを実行.

terminal1
$ cd ~/ros2_studies_ws/
$ . install/setup.bash
$ ros2 run minimal_parameter ros1_like_param_holder_test
terminal2
$ cd ~/ros2_studies_ws/
$ . install/setup.bash
$ ros2 run minimal_parameter ros1_like_param_async_user_test

terminal2に出てくるメッセージを見て考える.
またterminal1のparam_holderを終了させないまま,async_userを2回,3回実行させると結果はどうなるか?

メモ

parameterはserviceを提供する側とサービスを提供されるclient側がある.parameterの値を保持し他に与えるnodeはserviceとなり,それを参照したり値を変更したりするものがclientとなる.clientが他のnodeの場合,「serviceを行うnodeはだれか?」という名前の情報が必要である.一方で,自身で自身のparameterを使用する場合(つまりservice=clientとなる場合),別に名前を知る必要はなく,自分のparameterにアクセスすればよいだけである.このことから,service≠clientの場合,clientはAsyncClientsParameterとしてparameter clientの機能を持ち,serviceに接続しなければいけない.一方,service=clientの場合,自分のparameterにアクセスするのでnode->get_parameterやthis->parameter,はてはget_parameterのみでアクセスできる.

つまり同一nodeの場合,「他のノードのparameter機能に接続してパラメータをいじる」ではなく,「nodeのパラメータに直接アクセス」になる.よってこの場合,wait_for_serviceしなくていいんじゃなかろうか.いいんです.

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした