7.1.6-ROS 実行管理

launch ファイルによる ROS ノードの管理

ROS の launch ファイルは XML 形式のファイルで、複数の ROS ノードを効率的に起動・管理するために使用します。本セクションでは、launch ファイルで使用できる各種タグについて、属性と用途を説明します。

<launch> タグ

<launch> タグはすべての launch ファイルのルートで、他のすべてのタグのコンテナとして機能します。

1. 属性

  • deprecated="非推奨の説明"
    現在の launch ファイルが非推奨であることをユーザーに示します。

2. 子タグ

  • launch ファイル内の他のタグはすべて <launch> タグの子要素です。

例:

xml
<launch>
    <!-- Other tags go here -->
</launch>

<node> タグ

<node> タグは起動する ROS ノードを指定するために使用します。launch ファイルで最もよく使われるタグの一つです。roslaunch コマンドはノードを宣言順に起動することを保証しません。ノードの起動はマルチスレッドで行われるためです。

1. 属性

  • pkg="package_name"
    ノードが属するパッケージを指定します。

  • type="nodeType"
    ノードの型で、実行ファイル名に対応します。

  • name="nodeName"
    ROS ネットワークトポロジ内でのノード名。

  • args="xxx xxx xxx" (任意)
    ノードに引数を渡します。

  • machine="machine_name"
    ノードを起動するマシンを指定します。

  • respawn="true | false" (任意)
    ノードが終了した場合に自動再起動するかを指定します。

  • respawn_delay="N" (任意)
    respawn が true の場合、ノードを再起動するまでの待機時間 (秒)。

  • required="true | false" (任意)
    このノードが重要かどうか。true の場合、このノードが終了すると roslaunch プロセス全体が終了します。

  • ns="namespace" (任意)
    指定の名前空間でノードを起動します。

  • clear_params="true | false" (任意)
    ノード起動前にノードのプライベート名前空間内の全パラメータをクリアします。

  • output="log | screen" (任意)
    ログ出力先を指定します: ログファイル または 画面。デフォルトは log

2. 子タグ

  • env: 環境変数の設定に使用。
  • remap: トピックまたはサービス名のリマップに使用。
  • rosparam: パラメータの設定に使用。
  • param: パラメータの設定に使用。

例:

xml
<launch>
    <node name="node1" pkg="my_package" type="node_executable" output="screen" respawn="true" respawn_delay="5">
        <param name="param_name" value="param_value"/>
        <remap from="/old_topic" to="/new_topic"/>
    </node>
</launch>

<include> タグ

<include> タグは別の XML 形式の launch ファイルを現在の launch ファイルに取り込むために使用し、モジュール化と再利用可能な構成を可能にします。

1. 属性

  • file="$(find package_name)/path/to/file.launch"
    取り込む launch ファイルへのパスを指定します。

  • ns="namespace" (任意)
    指定の名前空間でファイルを取り込みます。

2. 子タグ

  • env: 環境変数の設定に使用。
  • arg: 取り込んだ launch ファイルに引数を渡すために使用。

例:

xml
<launch>
    <include file="$(find my_package)/launch/another_launch_file.launch" ns="my_namespace"/>
</launch>

<remap> タグ

<remap> タグは ROS のトピック名やサービス名をリマップするために使用します。名前の衝突を避けたり、複数ノード間で名前を統一したりするのに便利です。

1. 属性

  • from="xxx"
    元のトピック名またはサービス名。

  • to="yyy"
    トピックまたはサービスの新しい名前。

2. 子タグ

  • なし

例:

xml
<launch>
    <node name="node1" pkg="my_package" type="node_executable">
        <remap from="/old_topic" to="/new_topic"/>
    </node>
</launch>

<param> タグ

<param> タグは ROS パラメータサーバにパラメータを設定するために使用します。パラメータのソースはタグ内で直接指定するか、外部ファイルから読み込むことができます。<node> タグ内で使用された場合、パラメータはそのノードのプライベート名前空間に設定されます。

1. 属性

  • name="namespace/parameter_name"
    パラメータの名前。名前空間を含めることができます。

  • value="xxx" (任意)
    パラメータの値を定義します。省略する場合、外部ファイルをパラメータのソースとして指定する必要があります。

  • type="str | int | double | bool | yaml" (任意)
    パラメータの型を指定します。指定しない場合、roslaunch が値から推測します:

    • . を含む数値は浮動小数点 (double) として解析。
    • 文字列 "true" と "false" はブール値として解析 (大文字小文字を区別しない)。
    • その他はすべて文字列として解析。

2. 子タグ

  • なし

例:

xml
<launch>
    <node name="node1" pkg="my_package" type="node_executable">
        <param name="namespace/param_name" value="param_value" type="str"/>
    </node>
</launch>

<rosparam> タグ

<rosparam> タグでは、YAML ファイルからパラメータをロードしたり、YAML ファイルにエクスポートしたり、削除したりできます。<node> タグ内で使用すると、パラメータはプライベートとして扱われます。

1. 属性

  • command="load | dump | delete" (任意、デフォルトは load)
    実行する操作を指定します: ファイルからパラメータをロード、ファイルにエクスポート、または削除。

  • file="$(find package_name)/path/to/file.yaml"
    パラメータを読み書きする YAML ファイルを指定します。

  • param="parameter_name"
    パラメータ名。

  • ns="namespace" (任意)
    パラメータの名前空間を指定します。

2. 子タグ

  • なし

例:

xml
<launch>
    <rosparam file="$(find my_package)/config/params.yaml" command="load" ns="my_namespace"/>
</launch>

<group> タグ

<group> タグはノードや他のタグをグループ化し、グループ全体に名前空間や他の設定を適用できるようにします。

1. 属性

  • ns="namespace" (任意)
    グループ内のすべてのノードとパラメータに名前空間を適用します。

  • clear_params="true | false" (任意)
    グループの起動前に、そのグループの名前空間内の全パラメータをクリアします。重要なパラメータが削除される可能性があるため注意して使用してください。

2. 子タグ

  • <launch> タグを除く任意のタグを <group> の子にできます。

例:

xml
<launch>
    <group ns="my_namespace" clear_params="true">
        <node name="node1" pkg="my_package" type="node_executable"/>
        <node name="node2" pkg="my_package" type="node_executable"/>
    </group>
</launch>

<arg> タグ

<arg> タグは関数のパラメータのように、実行時に launch ファイルに渡せる動的な引数を定義するために使用します。これにより launch ファイルの柔軟性が高まります。

1. 属性

  • name="argument_name"
    引数名。

  • default="default_value" (任意)
    引数のデフォルト値を指定します。

  • value="value" (任意)
    引数の値を指定します。default と同時には使用できません。

  • doc="description"
    引数の説明を指定します。

2. 子タグ

  • なし

3. 例

引数構文を持つ launch ファイル hello.launch:

xml
<launch>
    <arg name="robot_name" default="my_robot"/>
    <param name="robot_name" value="$(arg robot_name)"/>
</launch>

引数を渡してコマンドラインから起動:

bash
roslaunch hello.launch robot_name:=robot_value

ROS ワークスペースのオーバーレイ

カスタムワークスペースが 2 つ、Workspace A と Workspace B があり、いずれにも turtlesim という名前のパッケージが含まれていると想像してください。さらに、システムの組み込みワークスペースにも turtlesim という名前のパッケージがあります。turtlesim パッケージを呼び出すと、どれが使用されるでしょうか?

実装手順

ステップ 0: ワークスペース A と B を作成

まず、Workspace A と Workspace B の 2 つのワークスペースを別々に作成します。各ワークスペース内で turtlesim という名前のパッケージを作成します。

ステップ 1: ~/.bashrc ファイルを編集

両方のワークスペースの setup ファイルを source するために、~/.bashrc ファイルに次の行を追加します:

bash
source /home/user/path/to/workspaceA/devel/setup.bash
source /home/user/path/to/workspaceB/devel/setup.bash

/home/user/path/to/ を実際のワークスペースのパスに置き換えてください。

ステップ 2: 環境変数を読み込む

新しいターミナルを開き、次のコマンドを実行して更新された環境変数を読み込みます:

bash
source ~/.bashrc

ステップ 3: ROS 環境変数を確認

ROS パッケージのパスを確認するには次を実行:

bash
echo $ROS_PACKAGE_PATH

結果: 出力は次の順で表示されます: Workspace B → Workspace A → システム組み込みワークスペース。

ステップ 4: turtlesim パッケージを呼び出す

次のコマンドで turtlesim パッケージに移動します:

bash
roscd turtlesim

結果: Workspace B 内の turtlesim パッケージに移動します。

ROS ノード名の衝突への対処

シナリオ

ROS では各ノードに名前があり、ノード初期化時に定義されます。C++ では ros::init(argc, argv, "node_name"); API を使用し、Python では rospy.init_node("node_name") を使用します。ROS のネットワークトポロジでは、複数ノードが同じ名前を共有すると呼び出し時に混乱を招くため、ノードは一意の名前を持たなければなりません。具体的には、重複した名前のノードを起動すると、既存の同名ノードは自動的にシャットダウンされます。しかし、同じノードの複数インスタンスを実行したい場合や、名前の衝突に対処したい場合はどうすればよいでしょうか?

ROS ではこのような状況に対処する 2 つの戦略を提供しています: 名前空間名前のリマップ

  • 名前空間 はノード名にプレフィックスを追加します。
  • 名前のリマップ はノード名にエイリアスを割り当てます。

両方の戦略でノード名の衝突を解決でき、複数の方法で実装できます:

  1. rosrun コマンドを使用。
  2. launch ファイルを通じて。
  3. ノードのコード内で。

このセクションではこれら 3 つの方法を使ってノード名の衝突を回避する方法を示します。

サンプルシナリオ

2 つの turtlesim_node ノードを起動してみましょう。何も変更せずに 2 つのターミナルでノードを起動した場合、2 つ目を起動すると最初のノードはシャットダウンされます。次のような警告メッセージが表示されます:

plaintext
[ WARN] [1578812836.351049332]: Shutdown request received.
[ WARN] [1578812836.351207362]: Reason given for shutdown: [new node registered with same name]

ノードは同じ名前を共有できないため、いくつかの戦略でこの問題に対処します。

rosrun を使った名前空間とリマップ

1. rosrun で名前空間を設定する

次の構文でノードに名前空間を設定できます:

bash
rosrun package_name node_name __ns:=/new_namespace

例:

bash
rosrun turtlesim turtlesim_node __ns:=/xxx
rosrun turtlesim turtlesim_node __ns:=/yyy

これらのコマンドにより、両方のノードが問題なく動作します。

結果:

rosnode list でノードを確認:

plaintext
/xxx/turtlesim
/yyy/turtlesim

2. rosrun でノード名をリマップする

次の構文でノードの名前をリマップ (実質的にエイリアスを与える) できます:

bash
rosrun package_name node_name __name:=new_name

例:

bash
rosrun turtlesim turtlesim_node __name:=t1
rosrun turtlesim turtlesim_node __name:=t2

これらのコマンドにより、両方のノードが新しい名前で動作します。

結果:

rosnode list でノードを確認:

plaintext
/t1
/t2

3. rosrun で名前空間と名前リマップを組み合わせる

両方の手法を組み合わせて、名前空間と名前リマップを同時に設定できます:

bash
rosrun package_name node_name __ns:=/new_namespace __name:=new_name

例:

bash
rosrun turtlesim turtlesim_node __ns:=/xxx __name:=tn

結果:

rosnode list でノードを確認:

plaintext
/xxx/tn

または、ノード起動前に環境変数を使って名前空間を設定することもできます:

bash
export ROS_NAMESPACE=xxxx

launch ファイルを使った名前空間とリマップ

launch ファイルでは <node> タグに namens という 2 つの重要な属性が含まれます。これらはそれぞれ名前のリマップと名前空間の設定に使用されます。launch ファイルで名前空間と名前リマップを扱うのは簡単です。

1. launch ファイルの例

launch ファイルで名前空間と名前リマップを設定する例:

xml
<launch>
    <node pkg="turtlesim" type="turtlesim_node" name="t1" />
    <node pkg="turtlesim" type="turtlesim_node" name="t2" />
    <node pkg="turtlesim" type="turtlesim_node" name="t1" ns="hello"/>
</launch>

この例では name 属性は必須で、ns は任意です。

2. launch ファイルの実行

launch ファイルを実行し、rosnode list で結果を確認します:

plaintext
/t1
/t2
/hello/t1

コードでの名前空間とリマップの設定

カスタムノードを実装する場合、コード内で名前空間と名前リマップを直接設定でき、より柔軟性があります。

1. C++ 実装: 名前のリマップ

次のコードで名前のエイリアスを設定できます:

cpp
ros::init(argc, argv, "zhangsan", ros::init_options::AnonymousName);

実行:

これにより、ノード名にタイムスタンプが追加され、一意性が保証されます。

2. C++ 実装: 名前空間の設定

コード内で次のように名前空間を直接設定できます:

cpp
std::map<std::string, std::string> map;
map["__ns"] = "xxxx";
ros::init(map, "wangqiang");

実行:

これによりノードに名前空間が設定され、衝突なく動作します。

3. Python 実装: 名前のリマップ

Python では次のコードで同様の機能を実現できます:

python
rospy.init_node("lisi", anonymous=True)

ROS におけるトピック名のリマップ

ROS では、トピック名のリマップにより、ノードのコードを変更せずにノードが購読・配信するトピック名を変更できます。これは、異なるトピック名で通信する必要がある複数のノードを統合する際に特に便利です。ROS でトピック名をリマップする主な方法は 3 つあります:

  1. rosrun コマンドを使用。
  2. launch ファイルを通じて。
  3. C++ または Python のコードを直接変更。

rosrun でトピックをリマップ

rosrun でトピック名をリマップする構文は:

bash
rosrun package_name node_name old_topic_name:=new_topic_name

例: teleop_twist_keyboardturtlesim の統合

teleop_twist_keyboard ノードと turtlesim 表示ノードの間で通信を確立する方法は 2 つあります:

1. 解決策 1: teleop_twist_keyboard のトピックをリマップ

このアプローチでは、teleop_twist_keyboard ノードのトピックを /turtle1/cmd_vel にリマップします。

  • キーボード制御ノードを起動:

    bash
    rosrun teleop_twist_keyboard teleop_twist_keyboard.py /cmd_vel:=/turtle1/cmd_vel
  • turtlesim 表示ノードを起動:

    bash
    rosrun turtlesim turtlesim_node

両方のノードは /turtle1/cmd_vel トピックを使って正しく通信します。

2. 解決策 2: turtlesim のトピックをリマップ

代わりに、turtlesim ノードのトピックを /cmd_vel にリマップできます。

  • キーボード制御ノードを起動:

    bash
    rosrun teleop_twist_keyboard teleop_twist_keyboard.py
  • turtlesim 表示ノードを起動:

    bash
    rosrun turtlesim turtlesim_node /turtle1/cmd_vel:=/cmd_vel

両方のノードは /cmd_vel トピックを使って正しく通信します。

launch ファイルでトピックをリマップ

launch ファイル内でトピックをリマップすることもできます。launch ファイル内でトピックをリマップする構文は:

xml
<node pkg="package_name" type="node_type" name="node_name">
    <remap from="original_topic" to="new_topic" />
</node>

例: launch ファイルを使った teleop_twist_keyboardturtlesim の統合

ここでも 2 つの解決策があります:

1. 解決策 1: teleop_twist_keyboard のトピックをリマップ

このアプローチでは、teleop_twist_keyboard ノードのトピックを /turtle1/cmd_vel にリマップします。

xml
<launch>
    <node pkg="turtlesim" type="turtlesim_node" name="t1" />
    <node pkg="teleop_twist_keyboard" type="teleop_twist_keyboard.py" name="key">
        <remap from="/cmd_vel" to="/turtle1/cmd_vel" />
    </node>
</launch>

両方のノードは正しく通信します。

2. 解決策 2: turtlesim のトピックをリマップ

このアプローチでは、turtlesim ノードのトピックを /cmd_vel にリマップします。

xml
<launch>
    <node pkg="turtlesim" type="turtlesim_node" name="t1">
        <remap from="/turtle1/cmd_vel" to="/cmd_vel" />
    </node>
    <node pkg="teleop_twist_keyboard" type="teleop_twist_keyboard.py" name="key" />
</launch>

両方のノードは正しく通信します。

コードでのトピックのリマップ

ROS のトピック名はノードの名前空間、ノードの名前、トピック自身の名前に影響されます。トピック名は一般に 3 つに分類できます:

  1. グローバル: トピック名は絶対的で / で始まり、ノードの名前空間に依存しません。
  2. 相対: トピック名は相対的で / で始まらず、ノードの名前空間内で解釈されます。
  3. プライベート: トピック名はプライベートで ~ で始まり、ノードのプライベート名前空間に対して解決されます。

C++ と Python の例でこれらの概念を見てみましょう。

1. C++ 実装

例の準備:

  1. 名前付きでノードを初期化:

    cpp
    ros::init(argc, argv, "hello");
  2. 異なる種類のトピック名を設定。

  3. ノード起動時に __ns:=xxx 引数を渡す。

  4. ノード起動後、rostopic でトピック情報を確認。

グローバルトピック名

グローバルトピック名は / で始まり、ノード名や名前空間に依存しません。

  • 例 1:

    cpp
    ros::Publisher pub = nh.advertise<std_msgs::String>("/chatter", 1000);

    結果: /chatter

  • 例 2:

    cpp
    ros::Publisher pub = nh.advertise<std_msgs::String>("/chatter/money", 1000);

    結果: /chatter/money

相対トピック名

相対トピック名は / で始まらず、ノードの名前空間に対して解決されます。

  • 例 1:

    cpp
    ros::Publisher pub = nh.advertise<std_msgs::String>("chatter", 1000);

    結果: xxx/chatter

  • 例 2:

    cpp
    ros::Publisher pub = nh.advertise<std_msgs::String>("chatter/money", 1000);

    結果: xxx/chatter/money

プライベートトピック名

プライベートトピック名は ~ で始まり、ノードのプライベート名前空間に対して解決されます。

  • 例 1:

    cpp
    ros::NodeHandle nh("~");
    ros::Publisher pub = nh.advertise<std_msgs::String>("chatter", 1000);

    結果: /xxx/hello/chatter

  • 例 2:

    cpp
    ros::NodeHandle nh("~");
    ros::Publisher pub = nh.advertise<std_msgs::String>("chatter/money", 1000);

    結果: /xxx/hello/chatter/money

  • 特殊なケース: ~ を使用する際、トピック名が / で始まる場合は絶対名として扱われます。

    cpp
    ros::NodeHandle nh("~");
    ros::Publisher pub = nh.advertise<std_msgs::String>("/chatter/money", 1000);

    結果: /chatter/money

Python 実装

例の準備:

  1. 名前付きでノードを初期化:

    python
    rospy.init_node("hello")
  2. 異なる種類のトピック名を設定。

  3. ノード起動時に __ns:=xxx 引数を渡す。

  4. ノード起動後、rostopic でトピック情報を確認。

グローバルトピック名

グローバルトピック名は / で始まり、ノード名や名前空間に依存しません。

  • 例 1:

    python
    pub = rospy.Publisher("/chatter", String, queue_size=1000)

    結果: /chatter

  • 例 2:

    python
    pub = rospy.Publisher("/chatter/money", String, queue_size=1000)

    結果: /chatter/money

相対トピック名

相対トピック名は / で始まらず、ノードの名前空間に対して解決されます。

  • 例 1:

    python
    pub = rospy.Publisher("chatter", String, queue_size=1000)

    結果: xxx/chatter

  • 例 2:

    python
    pub = rospy.Publisher("chatter/money", String, queue_size=1000)

    結果: xxx/chatter/money

プライベートトピック名

プライベートトピック名は ~ で始まり、ノードのプライベート名前空間に対して解決されます。

  • 例 1:

    python
    pub = rospy.Publisher("~chatter", String, queue_size=1000)

    結果: /xxx/hello/chatter

  • 例 2:

    python
    pub = rospy.Publisher("~chatter/money", String, queue_size=1000)

    結果: /xxx/hello/chatter/money


ROS でのパラメータ設定

ROS ではパラメータを使ってノードを実行時に設定します。rosrun コマンドの使用、launch ファイル内、コード内など、さまざまな方法で設定できます。パラメータは定義方法に応じてグローバル、相対、プライベートのいずれかになります。

rosrun でパラメータを設定

ノードを rosrun コマンドで起動するときにパラメータを設定できます。パラメータ設定の構文は:

bash
rosrun package_name node_name _parameter_name:=parameter_value

例: Turtlesim ノードにパラメータを設定

turtlesim_node を起動して、パラメータ A = 100 を設定しましょう。

bash
rosrun turtlesim turtlesim_node _A:=100

パラメータの確認

次のコマンドですべてのパラメータを表示し、結果を確認できます:

bash
rosparam list

出力:

plaintext
/turtlesim/A
/turtlesim/background_b
/turtlesim/background_g
/turtlesim/background_r

説明: パラメータ A にはノード名 (/turtlesim/) のプレフィックスが付いており、rosrun でパラメータを設定するときはプライベート名前空間モードで設定されることを示しています。

launch ファイルでのパラメータ設定

前述の通り、launch ファイルでは <param> または <rosparam> タグを使ってパラメータを設定できます。<node> タグの外側で設定されたパラメータはグローバル、<node> タグの内側で設定されたパラメータはプライベートで、ノードの名前空間に対して相対的になります。

例: <param> タグでパラメータを設定

グローバルパラメータとプライベートパラメータを設定する例:

xml
<launch>
    <param name="p1" value="100" />
    <node pkg="turtlesim" type="turtlesim_node" name="t1">
        <param name="p2" value="100" />
    </node>
</launch>

パラメータの確認

launch ファイルを実行した後、パラメータを次のコマンドで確認できます:

bash
rosparam list

出力:

plaintext
/p1
/t1/p2

説明: パラメータ p1 はグローバルで、p2t1 ノードに対してプライベートであることが名前空間から分かります。

コードでのパラメータ設定

コードでパラメータを設定すると、より柔軟にプログラム的にグローバル、相対、プライベートのパラメータを定義できます。

1. C++ 実装

C++ では ros::param API または ros::NodeHandle オブジェクトを通じてパラメータを設定できます。

1.1 ros::param を使ったパラメータ設定

ros::param::set 関数はパラメータを設定するために使用されます。第 1 引数はパラメータ名、第 2 引数は値です。パラメータ名が / で始まる場合はグローバル、~ で始まる場合はプライベート、それ以外は相対パラメータです。

例:

cpp
ros::param::set("/set_A", 100); // Global, independent of namespace and node name
ros::param::set("set_B", 100);  // Relative, dependent on namespace
ros::param::set("~set_C", 100); // Private, dependent on namespace and node name

名前空間が xxx、ノード名が yyy であると仮定して rosparam list でパラメータを確認すると次のように表示されます:

plaintext
/set_A
/xxx/set_B
/xxx/yyy/set_C

ros::NodeHandle を使ったパラメータ設定

ros::NodeHandle でパラメータを設定するには、まず NodeHandle オブジェクトを作成し、setParam メソッドを呼び出します。パラメータ名が / で始まる場合はグローバル、/ で始まらない場合は NodeHandle オブジェクトの作成方法によって相対かプライベートかが決まります。

例:

cpp
ros::NodeHandle nh;
nh.setParam("/nh_A", 100); // Global, independent of namespace and node name

nh.setParam("nh_B", 100);  // Relative, dependent on namespace

ros::NodeHandle nh_private("~");
nh_private.setParam("nh_C", 100); // Private, dependent on namespace and node name

名前空間が xxx、ノード名が yyy であると仮定して rosparam list でパラメータを確認すると次のように表示されます:

plaintext
/nh_A
/xxx/nh_B
/xxx/yyy/nh_C

2. Python 実装

Python では、パラメータの設定は C++ よりやや簡単です。rospy.set_param 関数でパラメータを設定します。第 1 引数はパラメータ名、第 2 引数は値です。C++ と同様、パラメータ名が / で始まる場合はグローバル、~ で始まる場合はプライベート、それ以外は相対です。

例:

python
rospy.set_param("/py_A", 100)  # Global, independent of namespace and node name
rospy.set_param("py_B", 100)   # Relative, dependent on namespace
rospy.set_param("~py_C", 100)  # Private, dependent on namespace and node name

名前空間が xxx、ノード名が yyy であると仮定して rosparam list でパラメータを確認すると次のように表示されます:

plaintext
/py_A
/xxx/py_B
/xxx/yyy/py_C