【ROS×Python】ROSパラメータ(Parameter)を実装しよう!

 ※ 本記事は広告・プロモーションを含みます。
Python

ROSパラメータの便利な使い方を解説します! たっきん( X (旧Twitter) )です!

今日はROS機能の1つ、「ROSパラメータ(Parameter)」についての解説と、サンプルコードを使った実装方法と動作を学んでいきましょう!

ROSパラメータ(Parameter)は簡単に説明するとノードの外部から書き換え可能な変数になります。

ROS2では各ノード毎に1つずつパラメータサーバーを持っており、このパラメータサーバーに任意のパラメータ(変数)を追加したり追加したパラメータ変数を書き換えたりすることができます。

上記の例ですと、ノードAは「param_a」、「param_b」、「param_c」の3つのパラメータ変数を持っており、ノードBは「param_x」、「param_y」、「param_z」の3つのパラメータ変数を持っています。

これらのパラメータ変数はノード起動前に値を設定することもできますし、ノード起動後にも値を変更することができます。

また、パラメータ変数はグローバル変数のように振舞うため、ノード内であればどこからでも値を取得することができます。

ROSパラメータの利点

僕がシステムトレードをROSを使って自作していることは既にご存じだと思いますが、シストレを自作していく上でROSパラメータを使用することで非常に便利だと感じた点があったので紹介したいと思います。

ROSパラメータの利点

  • パラメータ違いのノードを容易に量産できる

上記の利点を実例を使ってより分かりやすく説明します。

例えばボリンジャーバンド(略称:BB)を用いたシステムトレードを実装するとします。

ボリンジャーバンド(略称:BB)「1本の平均値と複数からなる標準偏差」から成るFXテクニカル指標の1つになります。
詳細は下記の記事を参照してほしいのですが、ここでは以下の2つの設定値によって挙動が決まるテクニカル指標だと思っていただければ大丈夫です。

ボリンジャーバンド設定値

  • 単純移動平均値の算出期間
  • 標準偏差σ

やりたいことはボリンジャーバンドのトレード手法は全く同じで設定値のみが異なるシステムトレードを複数同時に実行することになります。

設定値は下記の2種類でそれぞれ「トレード①」、「トレード②」として2つ同時に実行したいとします。

設定値トレード①トレード②
単純移動平均値の算出期間
(変数名:sma)
510
標準偏差σ
(変数名:sigma)
1.02.0

この場合、設定値違いの2つのトレードプログラムを作成しなければならないのでしょうか?

そんなことはありません。

トレード手法は全く同じでよいのであれば、作成するトレードプログラム(BBノード)は1つだけで良くて、あとはROSパラメータのみを変更したBBノードを2つ起動すれば容易にBBノードを量産することができます。

同じBBノードを起動するときは各ノードを名前空間(ネームスペース)で区切りましょう!
同じノード名を持つノードは同じ名前空間で起動するとノード名が一意に識別できなくなり不都合を生じるからです。

上記の図では「name_space_1」「name_space_2」という2つの名前空間内で各BBノードを起動しています。

ROSパラメータの実装例

それでは、実際にROSパラメータのプログラムを書いて、動作を確認してみましょう!

以降で紹介するサンプルコードは全てROSパッケージ化した状態でGitHubに載せています。

自分の環境で実行して挙動を確認してみたい場合は下記からクローンして使用してください。

本ページで使用するコード

作成するサンプルは前章で説明したボリンジャーバンド(略称:BB)ノードになります。

パラメータは下記の2つを定義します。

  • 単純移動平均値の算出期間(変数名:sma
  • 標準偏差σ(変数名:sigma

作成したコードは下記となります。

import rclpy
from rclpy.executors import ExternalShutdownException
from rclpy.node import Node
from rclpy.parameter import Parameter


class BbParameter(Node):
    """
    ボリンジャーバンド(BB)パラメータノード
    """

    def __init__(self) -> None:
        """
        ノードの初期化
        """
        super().__init__("bb_param")

        # ロガー取得
        self.logger = self.get_logger()

        # パラメータの定義
        self.declare_parameter("sma", Parameter.Type.INTEGER)
        self.declare_parameter("sigma", Parameter.Type.DOUBLE)

        # 1秒周期で実行されるROSタイマーの定義
        self.timer = self.create_timer(1, self._timer_callback)

    def _timer_callback(self) -> None:
        """
        timerコールバック
        """
        # パラメータの値を取得
        sma = self.get_parameter("sma").value
        sigma = self.get_parameter("sigma").value
        # 取得したパラメータの値をログ出力
        self.logger.info("sma:[{}] sigma:[{}]".format(sma, sigma))


def main(args: list[str] | None = None) -> None:
    # ROSの初期化
    rclpy.init(args=args)
    # ノードの作成
    bb = BbParameter()
    bb.get_logger().info("bb_param start!")

    try:
        # ノードの実行開始
        rclpy.spin(bb)
    except (KeyboardInterrupt, ExternalShutdownException):
        pass
    else:
        # ROSのシャットダウン
        rclpy.shutdown()
    finally:
        # ノードの破棄
        bb.destroy_node()

パラメータを使用するにはパラメータ名を宣言する必要があります。

実際に宣言しているのは21~23行目になります。

# パラメータの定義
self.declare_parameter("sma", Parameter.Type.INTEGER)
self.declare_parameter("sigma", Parameter.Type.DOUBLE)

パラメータ宣言はdeclare_parameter()関数を使用します。

引数には下記を指定します。

  • 第1引数:パラメータ名
  • 第2引数:パラメータ型 or 初期値

パラメータも変数になりますので型を定義する必要があります。

型は第2引数で指定する必要があり、Parameter.Type.***のように指定します。

指定できる型は下記になります。

Parameter.Type.***値の設定例
BOOLtrue
INTEGER101
DOUBLE25.34
STRING“example”
BYTE_ARRAY不明(※)
BOOL_ARRAY[true, false]
INTEGER_ARRAY[0, 2, 10]
DOUBLE_ARRAY[0.12, 1.63, 12.45]
STRING_ARRAY[“ex”, “am”, “ple”]

※:BYTE_ARRAY型について
具体的な設定値を調べてみましたが、わかりませんでした。
とは言え、BYTE_ARRAY型は基本的に使うことはないと思いますのであまり問題にはならないでしょう。

第2引数には初期値を指定することもできます。
初期値を設定した場合、ノード起動前にパラメータに値が設定されていなかった場合は初期値が設定されるようになります。
その際に、初期値の型がそのままパラメータの型として設定されます。
逆に第2引数にパラメータ型を指定した場合、ノード起動前にパラメータ値の設定が必須となります。
(設定されていないとParameterUninitializedExceptionが吐かれる)
後者の場合、例外によってパラメータ値が設定されていないことに気づくことができるため、第2引数にはパラメータ型を指定することを僕は推奨します。

25~26行目には1秒周期で実行されるROSタイマーを定義しています。

# 1秒周期で実行されるROSタイマーの定義
self.timer = self.create_timer(1, self._timer_callback)

タイマーで呼び出されたコールバック関数内は28~36行目で定義しており、ROSパラメータの値を取得してログ出力する処理となっています。

def _timer_callback(self) -> None:
    """
    timerコールバック
    """
    # パラメータの値を取得
    sma = self.get_parameter("sma").value
    sigma = self.get_parameter("sigma").value
    # 取得したパラメータの値をログ出力
    self.logger.info("sma:[{}] sigma:[{}]".format(sma, sigma))

パラメータ値の取得はget_parameter().value関数を使用します。

引数には取得したい「パラメータ名」を指定します。

実行してみる

では早速実行してみましょう!

ノード名はbb_paramです。

$ ros2 run ros2_example bb_param

実行結果は下記となるはずです。

[INFO][bb_param]: bb_param start!
Traceback (most recent call last):
  ・・・エラー・・・
  File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/node.py", line 598, in get_parameter
    raise ParameterUninitializedException(name)
rclpy.exceptions.ParameterUninitializedException: The parameter 'sma' is not initialized
[ros2run]: Process exited with failure 1

ParameterUninitializedExceptionが発生しましたね。

declare_parameter()でパラメータを宣言する際に、第2引数にパラメータ型を指定したためノード起動前にはパラメータ値の設定が必須になっています。

なので、パラメータ値を設定した上でノードを起動しましょう。

パラメータ値を設定するには、コマンドライン上に--ros-args -p <パラメータ名>:=<設定値>を追加する必要があります。

設定値は次の値とします。

  • パラメータ変数:sma = 5
  • パラメータ変数:sigma = 1.0

今回の例ですと下記のようになります。

$ ros2 run ros2_example bb_param --ros-args -p sma:=5 -p sigma:=1.0

実行結果が下記のようになれば成功です!

[INFO][bb_param]: bb_param start!
[INFO][bb_param]: sma:[5] sigma:[1.0]
[INFO][bb_param]: sma:[5] sigma:[1.0]
[INFO][bb_param]: sma:[5] sigma:[1.0]
[INFO][bb_param]: sma:[5] sigma:[1.0]

実行中のパラメータ値を変更してみる

ROSパラメータはノードが実行中であってもパラメータ値を変更することができます。

システムトレードのような使い方では実行中に値を変更するようなことは基本的にないのですが、一応やり方を紹介しますね!

bb_paramノードを起動している状態でrqtを起動します。

$ rqt

ウィンドウが立ち上がったら「Plugins」→「Configuration」→「Dynamic Reconfigure」の順にクリックしていきます。

すると下記のような画面になるので左側の「bb_param」を選択後、右側の「sma」の値を5→10に変更します。

すると下記のようにsmaの値が5→10に書き換わってログ出力されるようになります。

[INFO][bb_param]: bb_param start!
[INFO][bb_param]: sma:[5] sigma:[1.0]
[INFO][bb_param]: sma:[5] sigma:[1.0]
[INFO][bb_param]: sma:[5] sigma:[1.0]
[INFO][bb_param]: sma:[5] sigma:[1.0]
[INFO][bb_param]: sma:[5] sigma:[1.0]
[INFO][bb_param]: sma:[10] sigma:[1.0]
[INFO][bb_param]: sma:[10] sigma:[1.0]
[INFO][bb_param]: sma:[10] sigma:[1.0]
[INFO][bb_param]: sma:[10] sigma:[1.0]
[INFO][bb_param]: sma:[10] sigma:[1.0]

パラメータの値はコマンドラインからでも変更できます。
rqtが使えるのであれば、そちら使った方が利便性が高いですが、紹介だけしておきますね。

・パラメータの値を取得(現在値が5であることを確認)

$ ros2 param get /bb_param sma
Integer value is: 5

・パラメータの値を5→10に変更

$ ros2 param set /bb_param sma 10
Set parameter successful

・パラメータの値を取得(10に変更されていることを確認)

$ ros2 param get /bb_param sma
Integer value is: 10

パラメータ違いのノードを2つ起動してみる

冒頭でROSパラメータを使う利点として「パラメータ違いのノードを容易に量産できる」と紹介しました。

では実際にその利点を実感してみましょう!

前章で作成したbb_paramノードを下図のパラメータ値で2つ起動してみましょう!

まずは1つ目のノード(sma=5, sigma=1.0)を起動します。

ここで注意点として同じ空間に同名のノードは多数起動できないので名前空間(ネームスペース)で区切ります。

1つ目ノードの名前空間はname_space_1としてノードを起動します。

$ ros2 run ros2_example bb_param --ros-args -p sma:=5 -p sigma:=1.0 --remap __ns:=/name_space_1

無事に起動すれば下記のように表示されます。

[INFO][name_space_1.bb_param]: bb_param start!
[INFO][name_space_1.bb_param]: sma:[5] sigma:[1.0]
[INFO][name_space_1.bb_param]: sma:[5] sigma:[1.0]
[INFO][name_space_1.bb_param]: sma:[5] sigma:[1.0]
[INFO][name_space_1.bb_param]: sma:[5] sigma:[1.0]
[INFO][name_space_1.bb_param]: sma:[5] sigma:[1.0]
[INFO][name_space_1.bb_param]: sma:[5] sigma:[1.0]

今度は2つ目のノード(sma=10, sigma=2.0)を起動します。

名前空間はname_space_2とします。

$ ros2 run ros2_example bb_param --ros-args -p sma:=10 -p sigma:=2.0 --remap __ns:=/name_space_2

無事に起動すれば下記のように表示されます。

[INFO][name_space_2.bb_param]: bb_param start!
[INFO][name_space_2.bb_param]: sma:[10] sigma:[2.0]
[INFO][name_space_2.bb_param]: sma:[10] sigma:[2.0]
[INFO][name_space_2.bb_param]: sma:[10] sigma:[2.0]
[INFO][name_space_2.bb_param]: sma:[10] sigma:[2.0]
[INFO][name_space_2.bb_param]: sma:[10] sigma:[2.0]
[INFO][name_space_2.bb_param]: sma:[10] sigma:[2.0]

bb_paramノードが2つ起動していることをrqtで確認してみましょう。

$ rqt

「Node Graph」「Parameter Reconfigure」を表示して下図のようになっていればパラメータ違いのbb_paramノードが2つ実行されています。

このように、同じノードをパラメータ違いで量産して起動する方法はとても簡単に行えることが伝わったかと思います。

もし、あなたもROSを使ったシステムトレードを自作する場合、ROSパラメータを上手く活用してノードを使いまわせるように作り込みましょう!

そうすれば、開発の負担が減らせると思いますよ!

<活用術>ROSパラメータをYAMLで設定

ROSパラメータの活用術を1つ紹介しますね!

ここまでROSパラメータの設定値はコマンドライン上で指定してきました。

$ ros2 run ros2_example bb_param --ros-args -p sma:=5 -p sigma:=1.0 --remap __ns:=/name_space_1

上記例ではパラメータ数が2つしかないのであまり問題にはならないのですが、パラメータ数が10個や20個とかになってくるとコマンドライン上で全て指定するのはあまりにも苦行ですよね。

ROSパラメータはYAMLファイル形式でもパラメータを設定できるので、必要であればYAMLを使うようにした方が良いです。

YAMLの書き出し

ROSパラメータで使用するYAMLファイルのフォーマットは決まっているのでフォーマット通りに手入力しても良いですが、手っ取り早く作るなら実行中のノードからYAML書き出しを行った方が良いです。

実行中ノードからのYAML書き出しコマンドは下記となります。

$ ros2 param dump <実行中ノード名> > <(出力先ディレクトリ)/(YAMLファイル名).yaml>

実例で解説していきます。

まずは下記コマンドでノードを起動しましょう。

$ ros2 run ros2_example bb_param --ros-args -p sma:=5 -p sigma:=1.0 --remap __ns:=/name_space_1

ノードが起動したら下記のコマンドを実行します。

$ ros2 param dump /name_space_1/bb_param > (出力先ディレクトリ)/ns1_bb_param.yaml

すると”(出力先ディレクトリ)”で指定した場所にYAMLファイルが書き出されます。

今回の例ですとファイル名は「ns1_bb_param.yaml」で中身は下記のようになります。

/name_space_1/bb_param:
  ros__parameters:
    sigma: 1.0
    sma: 5
    use_sim_time: false

YAMLの読み込み

次はYAMLファイルからROSパラメータ値を設定してノードが起動されるようにしてみます。

コマンドは下記となります。

$ ros2 run ros2_example bb_param --ros-args --params-file (YAML保存先ディレクトリ)/ns1_bb_param.yaml --remap __ns:=/name_space_1

ノードが起動するとYAMLのパラメータ値が設定された状態でノードが起動します。

[INFO][name_space_1.bb_param]: bb_param start!
[INFO][name_space_1.bb_param]: sma:[5] sigma:[1.0]
[INFO][name_space_1.bb_param]: sma:[5] sigma:[1.0]
[INFO][name_space_1.bb_param]: sma:[5] sigma:[1.0]
[INFO][name_space_1.bb_param]: sma:[5] sigma:[1.0]
[INFO][name_space_1.bb_param]: sma:[5] sigma:[1.0]
[INFO][name_space_1.bb_param]: sma:[5] sigma:[1.0]

まとめ

ROSパラメータ(Parameter)

  • declare_parameter()でパラメータ名の宣言
    • 第1引数:パラメータ名
    • 第2引数:パラメータ型 or 初期値
  • get_parameter().value関数でパラメータ値の取得
    • 第1引数:パラメータ名
スポンサーリンク

コメント

タイトルとURLをコピーしました