7.1.6-ROS 実行管理
launch ファイルによる ROS ノードの管理
ROS の launch ファイルは XML 形式のファイルで、複数の ROS ノードを効率的に起動・管理するために使用します。本セクションでは、launch ファイルで使用できる各種タグについて、属性と用途を説明します。
<launch> タグ
<launch> タグはすべての launch ファイルのルートで、他のすべてのタグのコンテナとして機能します。
1. 属性
- deprecated="非推奨の説明"
現在の launch ファイルが非推奨であることをユーザーに示します。
2. 子タグ
- launch ファイル内の他のタグはすべて
<launch>タグの子要素です。
例:
<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: パラメータの設定に使用。
例:
<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 ファイルに引数を渡すために使用。
例:
<launch>
<include file="$(find my_package)/launch/another_launch_file.launch" ns="my_namespace"/>
</launch><remap> タグ
<remap> タグは ROS のトピック名やサービス名をリマップするために使用します。名前の衝突を避けたり、複数ノード間で名前を統一したりするのに便利です。
1. 属性
-
from="xxx"
元のトピック名またはサービス名。 -
to="yyy"
トピックまたはサービスの新しい名前。
2. 子タグ
- なし
例:
<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. 子タグ
- なし
例:
<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. 子タグ
- なし
例:
<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>の子にできます。
例:
<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:
<launch>
<arg name="robot_name" default="my_robot"/>
<param name="robot_name" value="$(arg robot_name)"/>
</launch>引数を渡してコマンドラインから起動:
roslaunch hello.launch robot_name:=robot_valueROS ワークスペースのオーバーレイ
カスタムワークスペースが 2 つ、Workspace A と Workspace B があり、いずれにも turtlesim という名前のパッケージが含まれていると想像してください。さらに、システムの組み込みワークスペースにも turtlesim という名前のパッケージがあります。turtlesim パッケージを呼び出すと、どれが使用されるでしょうか?
実装手順
ステップ 0: ワークスペース A と B を作成
まず、Workspace A と Workspace B の 2 つのワークスペースを別々に作成します。各ワークスペース内で turtlesim という名前のパッケージを作成します。
ステップ 1: ~/.bashrc ファイルを編集
両方のワークスペースの setup ファイルを source するために、~/.bashrc ファイルに次の行を追加します:
source /home/user/path/to/workspaceA/devel/setup.bash
source /home/user/path/to/workspaceB/devel/setup.bash/home/user/path/to/ を実際のワークスペースのパスに置き換えてください。
ステップ 2: 環境変数を読み込む
新しいターミナルを開き、次のコマンドを実行して更新された環境変数を読み込みます:
source ~/.bashrcステップ 3: ROS 環境変数を確認
ROS パッケージのパスを確認するには次を実行:
echo $ROS_PACKAGE_PATH結果: 出力は次の順で表示されます: Workspace B → Workspace A → システム組み込みワークスペース。
ステップ 4: turtlesim パッケージを呼び出す
次のコマンドで turtlesim パッケージに移動します:
roscd turtlesim結果: Workspace B 内の turtlesim パッケージに移動します。
ROS ノード名の衝突への対処
シナリオ
ROS では各ノードに名前があり、ノード初期化時に定義されます。C++ では ros::init(argc, argv, "node_name"); API を使用し、Python では rospy.init_node("node_name") を使用します。ROS のネットワークトポロジでは、複数ノードが同じ名前を共有すると呼び出し時に混乱を招くため、ノードは一意の名前を持たなければなりません。具体的には、重複した名前のノードを起動すると、既存の同名ノードは自動的にシャットダウンされます。しかし、同じノードの複数インスタンスを実行したい場合や、名前の衝突に対処したい場合はどうすればよいでしょうか?
ROS ではこのような状況に対処する 2 つの戦略を提供しています: 名前空間 と 名前のリマップ。
- 名前空間 はノード名にプレフィックスを追加します。
- 名前のリマップ はノード名にエイリアスを割り当てます。
両方の戦略でノード名の衝突を解決でき、複数の方法で実装できます:
rosrunコマンドを使用。- launch ファイルを通じて。
- ノードのコード内で。
このセクションではこれら 3 つの方法を使ってノード名の衝突を回避する方法を示します。
サンプルシナリオ
2 つの turtlesim_node ノードを起動してみましょう。何も変更せずに 2 つのターミナルでノードを起動した場合、2 つ目を起動すると最初のノードはシャットダウンされます。次のような警告メッセージが表示されます:
[ WARN] [1578812836.351049332]: Shutdown request received.
[ WARN] [1578812836.351207362]: Reason given for shutdown: [new node registered with same name]ノードは同じ名前を共有できないため、いくつかの戦略でこの問題に対処します。
rosrun を使った名前空間とリマップ
1. rosrun で名前空間を設定する
次の構文でノードに名前空間を設定できます:
rosrun package_name node_name __ns:=/new_namespace例:
rosrun turtlesim turtlesim_node __ns:=/xxx
rosrun turtlesim turtlesim_node __ns:=/yyyこれらのコマンドにより、両方のノードが問題なく動作します。
結果:
rosnode list でノードを確認:
/xxx/turtlesim
/yyy/turtlesim2. rosrun でノード名をリマップする
次の構文でノードの名前をリマップ (実質的にエイリアスを与える) できます:
rosrun package_name node_name __name:=new_name例:
rosrun turtlesim turtlesim_node __name:=t1
rosrun turtlesim turtlesim_node __name:=t2これらのコマンドにより、両方のノードが新しい名前で動作します。
結果:
rosnode list でノードを確認:
/t1
/t23. rosrun で名前空間と名前リマップを組み合わせる
両方の手法を組み合わせて、名前空間と名前リマップを同時に設定できます:
rosrun package_name node_name __ns:=/new_namespace __name:=new_name例:
rosrun turtlesim turtlesim_node __ns:=/xxx __name:=tn結果:
rosnode list でノードを確認:
/xxx/tnまたは、ノード起動前に環境変数を使って名前空間を設定することもできます:
export ROS_NAMESPACE=xxxxlaunch ファイルを使った名前空間とリマップ
launch ファイルでは <node> タグに name と ns という 2 つの重要な属性が含まれます。これらはそれぞれ名前のリマップと名前空間の設定に使用されます。launch ファイルで名前空間と名前リマップを扱うのは簡単です。
1. launch ファイルの例
launch ファイルで名前空間と名前リマップを設定する例:
<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 で結果を確認します:
/t1
/t2
/hello/t1コードでの名前空間とリマップの設定
カスタムノードを実装する場合、コード内で名前空間と名前リマップを直接設定でき、より柔軟性があります。
1. C++ 実装: 名前のリマップ
次のコードで名前のエイリアスを設定できます:
ros::init(argc, argv, "zhangsan", ros::init_options::AnonymousName);実行:
これにより、ノード名にタイムスタンプが追加され、一意性が保証されます。
2. C++ 実装: 名前空間の設定
コード内で次のように名前空間を直接設定できます:
std::map<std::string, std::string> map;
map["__ns"] = "xxxx";
ros::init(map, "wangqiang");実行:
これによりノードに名前空間が設定され、衝突なく動作します。
3. Python 実装: 名前のリマップ
Python では次のコードで同様の機能を実現できます:
rospy.init_node("lisi", anonymous=True)ROS におけるトピック名のリマップ
ROS では、トピック名のリマップにより、ノードのコードを変更せずにノードが購読・配信するトピック名を変更できます。これは、異なるトピック名で通信する必要がある複数のノードを統合する際に特に便利です。ROS でトピック名をリマップする主な方法は 3 つあります:
rosrunコマンドを使用。- launch ファイルを通じて。
- C++ または Python のコードを直接変更。
rosrun でトピックをリマップ
rosrun でトピック名をリマップする構文は:
rosrun package_name node_name old_topic_name:=new_topic_name例: teleop_twist_keyboard と turtlesim の統合
teleop_twist_keyboard ノードと turtlesim 表示ノードの間で通信を確立する方法は 2 つあります:
1. 解決策 1: teleop_twist_keyboard のトピックをリマップ
このアプローチでは、teleop_twist_keyboard ノードのトピックを /turtle1/cmd_vel にリマップします。
-
キーボード制御ノードを起動:
bashrosrun teleop_twist_keyboard teleop_twist_keyboard.py /cmd_vel:=/turtle1/cmd_vel -
turtlesim 表示ノードを起動:
bashrosrun turtlesim turtlesim_node
両方のノードは /turtle1/cmd_vel トピックを使って正しく通信します。
2. 解決策 2: turtlesim のトピックをリマップ
代わりに、turtlesim ノードのトピックを /cmd_vel にリマップできます。
-
キーボード制御ノードを起動:
bashrosrun teleop_twist_keyboard teleop_twist_keyboard.py -
turtlesim 表示ノードを起動:
bashrosrun turtlesim turtlesim_node /turtle1/cmd_vel:=/cmd_vel
両方のノードは /cmd_vel トピックを使って正しく通信します。
launch ファイルでトピックをリマップ
launch ファイル内でトピックをリマップすることもできます。launch ファイル内でトピックをリマップする構文は:
<node pkg="package_name" type="node_type" name="node_name">
<remap from="original_topic" to="new_topic" />
</node>例: launch ファイルを使った teleop_twist_keyboard と turtlesim の統合
ここでも 2 つの解決策があります:
1. 解決策 1: teleop_twist_keyboard のトピックをリマップ
このアプローチでは、teleop_twist_keyboard ノードのトピックを /turtle1/cmd_vel にリマップします。
<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 にリマップします。
<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 つに分類できます:
- グローバル: トピック名は絶対的で
/で始まり、ノードの名前空間に依存しません。 - 相対: トピック名は相対的で
/で始まらず、ノードの名前空間内で解釈されます。 - プライベート: トピック名はプライベートで
~で始まり、ノードのプライベート名前空間に対して解決されます。
C++ と Python の例でこれらの概念を見てみましょう。
1. C++ 実装
例の準備:
-
名前付きでノードを初期化:
cppros::init(argc, argv, "hello"); -
異なる種類のトピック名を設定。
-
ノード起動時に
__ns:=xxx引数を渡す。 -
ノード起動後、
rostopicでトピック情報を確認。
グローバルトピック名
グローバルトピック名は / で始まり、ノード名や名前空間に依存しません。
-
例 1:
cppros::Publisher pub = nh.advertise<std_msgs::String>("/chatter", 1000);結果:
/chatter -
例 2:
cppros::Publisher pub = nh.advertise<std_msgs::String>("/chatter/money", 1000);結果:
/chatter/money
相対トピック名
相対トピック名は / で始まらず、ノードの名前空間に対して解決されます。
-
例 1:
cppros::Publisher pub = nh.advertise<std_msgs::String>("chatter", 1000);結果:
xxx/chatter -
例 2:
cppros::Publisher pub = nh.advertise<std_msgs::String>("chatter/money", 1000);結果:
xxx/chatter/money
プライベートトピック名
プライベートトピック名は ~ で始まり、ノードのプライベート名前空間に対して解決されます。
-
例 1:
cppros::NodeHandle nh("~"); ros::Publisher pub = nh.advertise<std_msgs::String>("chatter", 1000);結果:
/xxx/hello/chatter -
例 2:
cppros::NodeHandle nh("~"); ros::Publisher pub = nh.advertise<std_msgs::String>("chatter/money", 1000);結果:
/xxx/hello/chatter/money -
特殊なケース:
~を使用する際、トピック名が/で始まる場合は絶対名として扱われます。cppros::NodeHandle nh("~"); ros::Publisher pub = nh.advertise<std_msgs::String>("/chatter/money", 1000);結果:
/chatter/money
Python 実装
例の準備:
-
名前付きでノードを初期化:
pythonrospy.init_node("hello") -
異なる種類のトピック名を設定。
-
ノード起動時に
__ns:=xxx引数を渡す。 -
ノード起動後、
rostopicでトピック情報を確認。
グローバルトピック名
グローバルトピック名は / で始まり、ノード名や名前空間に依存しません。
-
例 1:
pythonpub = rospy.Publisher("/chatter", String, queue_size=1000)結果:
/chatter -
例 2:
pythonpub = rospy.Publisher("/chatter/money", String, queue_size=1000)結果:
/chatter/money
相対トピック名
相対トピック名は / で始まらず、ノードの名前空間に対して解決されます。
-
例 1:
pythonpub = rospy.Publisher("chatter", String, queue_size=1000)結果:
xxx/chatter -
例 2:
pythonpub = rospy.Publisher("chatter/money", String, queue_size=1000)結果:
xxx/chatter/money
プライベートトピック名
プライベートトピック名は ~ で始まり、ノードのプライベート名前空間に対して解決されます。
-
例 1:
pythonpub = rospy.Publisher("~chatter", String, queue_size=1000)結果:
/xxx/hello/chatter -
例 2:
pythonpub = rospy.Publisher("~chatter/money", String, queue_size=1000)結果:
/xxx/hello/chatter/money
ROS でのパラメータ設定
ROS ではパラメータを使ってノードを実行時に設定します。rosrun コマンドの使用、launch ファイル内、コード内など、さまざまな方法で設定できます。パラメータは定義方法に応じてグローバル、相対、プライベートのいずれかになります。
rosrun でパラメータを設定
ノードを rosrun コマンドで起動するときにパラメータを設定できます。パラメータ設定の構文は:
rosrun package_name node_name _parameter_name:=parameter_value例: Turtlesim ノードにパラメータを設定
turtlesim_node を起動して、パラメータ A = 100 を設定しましょう。
rosrun turtlesim turtlesim_node _A:=100パラメータの確認
次のコマンドですべてのパラメータを表示し、結果を確認できます:
rosparam list出力:
/turtlesim/A
/turtlesim/background_b
/turtlesim/background_g
/turtlesim/background_r説明: パラメータ A にはノード名 (/turtlesim/) のプレフィックスが付いており、rosrun でパラメータを設定するときはプライベート名前空間モードで設定されることを示しています。
launch ファイルでのパラメータ設定
前述の通り、launch ファイルでは <param> または <rosparam> タグを使ってパラメータを設定できます。<node> タグの外側で設定されたパラメータはグローバル、<node> タグの内側で設定されたパラメータはプライベートで、ノードの名前空間に対して相対的になります。
例: <param> タグでパラメータを設定
グローバルパラメータとプライベートパラメータを設定する例:
<launch>
<param name="p1" value="100" />
<node pkg="turtlesim" type="turtlesim_node" name="t1">
<param name="p2" value="100" />
</node>
</launch>パラメータの確認
launch ファイルを実行した後、パラメータを次のコマンドで確認できます:
rosparam list出力:
/p1
/t1/p2説明: パラメータ p1 はグローバルで、p2 は t1 ノードに対してプライベートであることが名前空間から分かります。
コードでのパラメータ設定
コードでパラメータを設定すると、より柔軟にプログラム的にグローバル、相対、プライベートのパラメータを定義できます。
1. C++ 実装
C++ では ros::param API または ros::NodeHandle オブジェクトを通じてパラメータを設定できます。
1.1 ros::param を使ったパラメータ設定
ros::param::set 関数はパラメータを設定するために使用されます。第 1 引数はパラメータ名、第 2 引数は値です。パラメータ名が / で始まる場合はグローバル、~ で始まる場合はプライベート、それ以外は相対パラメータです。
例:
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 でパラメータを確認すると次のように表示されます:
/set_A
/xxx/set_B
/xxx/yyy/set_Cros::NodeHandle を使ったパラメータ設定
ros::NodeHandle でパラメータを設定するには、まず NodeHandle オブジェクトを作成し、setParam メソッドを呼び出します。パラメータ名が / で始まる場合はグローバル、/ で始まらない場合は NodeHandle オブジェクトの作成方法によって相対かプライベートかが決まります。
例:
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 でパラメータを確認すると次のように表示されます:
/nh_A
/xxx/nh_B
/xxx/yyy/nh_C2. Python 実装
Python では、パラメータの設定は C++ よりやや簡単です。rospy.set_param 関数でパラメータを設定します。第 1 引数はパラメータ名、第 2 引数は値です。C++ と同様、パラメータ名が / で始まる場合はグローバル、~ で始まる場合はプライベート、それ以外は相対です。
例:
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 でパラメータを確認すると次のように表示されます:
/py_A
/xxx/py_B
/xxx/yyy/py_C