メモ:未整理の情報
ここではparameterに関するプログラムを作成する.
特にparameter clientとして非同期に取得・設定を行うAsyncParameterClientと同期的に行うSyncParameterClientを紹介する.
基本的には,宣言・値の設定・取得となり,それぞれ以下の関数となる.詳しくは以下の通り.
- 宣言
- declare_parameter
- パラメータの宣言
- dashing diademataから宣言なしでは値の設定・取得ができなくなった.これにより「パラメータの名前を打ち間違っちゃって似た名前のものが複数できる」などがなくなった.
- undeclare_parameter
- パラメータの無効化
- has_parameter
- パラメータの存在確認
- declare_parameter
- 値の設定
- set_parameters
- 値の取得
- get_parameter
- get_parameters
2019/6/13現在,公式のAPIには無いようである. 2023/01/21 存在を確認.
公式のRoadmapに詳しく書いてある.
もしくは公式githubのrcl_interfaces.
この他,parameter serviceや参照しているparameterが変更された場合に処理を行うcallback関数の機能,パラメータの権限に関するあれやこれやなどあるが,future worksで.
準備
$ 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
#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
#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 ¶m : 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 ¶m_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 ¶m : 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
#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;
}
概要
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>
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}
)
ビルド・実行
ビルド
$ 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を実行.
$ cd ~/ros2_studies_ws/
$ . install/setup.bash
$ ros2 run minimal_parameter ros1_like_param_holder_test
$ 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しなくていいんじゃなかろうか.いいんです.