【ROS2関係トップページへ】
【ROS2レクチャー:初級 -ROS1 style-】
【前:ROS2のparameter概要】
【次:YAMLファイルによるROS2のパラメータ設定】
ここではparameterに関するservice側とclietn側のプログラムを作成する.
clientのプログラムとしては非同期に取得・設定を行うAsyncParameterClientと同期的に行うSyncParameterClientがある.ここでは同期型のSyncParameterClientを紹介する.
基本的な機能は,パラメータの宣言・値の設定・取得となり,それぞれ以下の関数となる.詳しくは以下の通り.
- 宣言
- declare_parameter
- パラメータの宣言
- dashing diademataから宣言なしでは値の設定・取得ができなくなった.これにより「パラメータの名前を打ち間違っちゃって似た名前のものが複数できる」などがなくなった.
- undeclare_parameter
- パラメータの無効化
- has_parameter
- パラメータの存在確認
- declare_parameter
- 値の設定
- set_parameters
- 値の取得
- get_parameter
- get_parameters
ROS2のparameter概要の記載通りserviceとしてのparameterとclientとしてのparameterがあるので公式のAPIのrclcpp::NodeとParameters:に記載がある.
parameter serviceについてはfuture worksで.
準備
$ cd ~/ros2_studies_ws/
$ ros2 pkg create minimal_parameter_ros1_like --dependencies rclcpp
Parameter
作成物:
- src/ros1_like_parameter_holder_main.cpp
- パラメータを保持するnode
- node名: param_holder
- 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
#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せずparam_clientがしていることに注意).また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_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 ¶m : 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 ¶m_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 ¶m : 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;
}
概要
param_holderに接続し,そのnodeが持っているパラメータを参照・変更・また参照するプログラム.
パラメータを取得・変更する場合,パラメータを保持しているnodeと接続しなければいけない.そのため,8行目のようにparameter clientとしての設定を行っている.引数は二つであり,一つ目がSyncParametersClientの機能を持たせるnodeへのポインタ,二つ目がparameterのデータを保持しているparameter nodeの名前(serviceとなるnodeの名前)である.
同期型なので,spin関数を用いなくてもget_parametersやset_parametersなどを実行したらすぐに取得・設定が行われる.
またget_parameter(get_parameters)で得られた返り値は型や名前など色々な情報を含む.パラメータの値そのものを得るためには,param.value_to_string()などの関数を使用する.代表的なものは以下の通り.その他は公式のAPIのrclcpp::Parameterを参照.
- param.as_bool()
- param.as_int()
- param.as_double()
- param.as_string()
説明
8行目:SyncParametersClientとして設定.二つ目の引数で参照するparameter nodeの名前を渡している.
9~16行目:parameterの取得,表示.
18~27行目:parameterのlist.詳細情報?18行目の二つ目の引数10は深さで,対象parameterの名前を深さ10まで取得するらしい.
29~40行目:paramter設定.値を変えることは可能(30,31行目).また値の種類を超えて変えることも可能(32行目).しかし新しいキーを設定することはできない模様(エラーにはならない).
42~51行目:parameter取得.
package.xmlとCMakeLists.txt
<package format="3">
<depend>rclcpp></depend>
find_package(rclcpp REQUIRED)
add_executable(ros1_like_param_holder_test
src/ros1_like_parameter_holder_main.cpp
)
ament_target_dependencies(ros1_like_param_holder_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
ros1_like_param_holder_test
ros1_like_param_sync_user_test
DESTINATION lib/${PROJECT_NAME}
)
ビルド・実行
ビルド
$ cd ~/ros2_studies_ws/
$ colcon build --symlink-install --packages-up-to minimal_parameter_ros1_like
$ . install/local_setup.bash
実行
param_holderとuser用のterminalを起動.
$ cd ~/ros2_studies_ws/
$ . install/local_setup.bash
$ ros2 run minimal_parameter_ros1_like ros1_like_param_holder_test
$ cd ~/ros2_studies_ws/
$ . install/local_setup.bash
$ ros2 run minimal_parameter_ros1_like ros1_like_param_sync_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しなくていいんじゃなかろうか.いいんです.