ROS2の最小構成parameter:初級 -ROS1 style-

【[ROS2関係トップページへ](https://qiita.com/NeK/items/7ac0f4ec10d51dbca084)】
【[ROS2レクチャー:初級 -ROS1 style-](https://qiita.com/NeK/items/74e4bb9d1530e22a63fc)】

【前:[ROS2のparameter概要]】
【次:[YAMLファイルによるROS2のパラメータ設定]】

[ROS2のparameter概要]:https://qiita.com/NeK/items/556a38782ce65bb81340
[YAMLファイルによるROS2のパラメータ設定]:https://qiita.com/NeK/items/15bf1e657d8d694592ed

ここではparameterに関するservice側とclietn側のプログラムを作成する．
clientのプログラムとしては非同期に取得・設定を行うAsyncParameterClientと同期的に行うSyncParameterClientがある．ここでは同期型のSyncParameterClientを紹介する．

基本的な機能は，パラメータの宣言・値の設定・取得となり，それぞれ以下の関数となる．詳しくは以下の通り．

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

[ROS2のparameter概要]の記載通りserviceとしてのparameterとclientとしてのparameterがあるので[公式のAPI][]のrclcpp::NodeとParameters:に記載がある．

parameter serviceについてはfuture worksで．

# 準備

```Bash:terminal
$ 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

```c++: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

```c++: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;
}
```

### 概要
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

```xml::package.xml
<package format="3">
  <depend>rclcpp></depend>
```

```CMakeLists.txt
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}
)
```

# ビルド・実行
## ビルド

```Bash:terminal
$ cd ~/ros2_studies_ws/
$ colcon build --symlink-install --packages-up-to minimal_parameter_ros1_like
$ . install/local_setup.bash
```

## 実行
param_holderとuser用のterminalを起動．

```Bash:terminal1
$ cd ~/ros2_studies_ws/
$ . install/local_setup.bash
$ ros2 run minimal_parameter_ros1_like ros1_like_param_holder_test
```
```Bash:terminal2
$ 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しなくていいんじゃなかろうか．いいんです．



[公式のAPI]:http://docs.ros2.org/foxy/api/rclcpp/index.html

【前:[ROS2のparameter概要]】
【次:[YAMLファイルによるROS2のパラメータ設定]】