ROS2 Tutorialsをやってみた ~Launchファイルの作成~
今回は以下を実施します。
- ROS 2 Tutorials (気になるところをメモします)
- Introspection with command-line tools
- Passing ROS arguments to nodes via the command-line
- Launching/monitoring multiple nodes with Launch
- The launch documentation (気になるところをメモします)
- Launchファイルの作成
- Publisher、Subscriberの起動
- LogInfoの実行
- TimerActionの実行
前回までのあらすじ
前回はColconのTutorialと、パッケージの作成をしました。
ROS 2 Tutorials
このページを実施します。
Tutorials · ros2/ros2 Wiki · GitHub
Introspection with command-line tools
- ROS2ではROS1のようなマスターが存在しないので、ノードの発見に時間がかかる
- その対策としてデーモンを起動している
ros2
ツールはプラグインによって拡張できる
デーモンの確認
# ターミナルを初めて起動したあと $ . ~/ros2_overlay_ws/install/setup.zsh $ ros2 daemon status The daemon is not running # トピックを流してみる $ ros2 topic pub /chatter std_msgs/String "data: Hello world" publisher: beginning loop publishing #1: std_msgs.msg.String(data='Hello world') publishing #2: std_msgs.msg.String(data='Hello world') publishing #3: std_msgs.msg.String(data='Hello world') publishing #4: std_msgs.msg.String(data='Hello world') ^C # デーモンが起動したことの確認 $ ros2 daemon status The daemon is not running
Passing ROS arguments to nodes via the command-line
Name remapping
- ノードに関わる名前(ノード名、トピック名、サービス名)は静的に変更できる
- 動的に変更できない
$ ros2 run demo_nodes_cpp talker __ns:=/demo __node:=my_talker chatter:=my_topic
- 詳細はここに書いてある
- Remapping Names
- ROS2では1つのプロセスで複数のノードを起動できる。それに対しても名前変更可能
- 詳細はCompositionのチュートリアルで理解する
$ ros2 run composition manual_composition talker:__node:=my_talker listener:__node:=my_listener
Logger configuration
- ログのレベルを引数で指定できる(詳細はLoggingのデモで理解する)
$ ros2 run demo_nodes_cpp listener __log_level:=debug
Paramteres
Launching/monitoring multiple nodes with Launch
- ROS2のLaunchファイルはpythonで記述する
- イベントに合わせてノードを起動したり止めたりできる
- launchファイルの例(新機能Lifecycleが練り込まれてるのでチョット複雑)
- launch/lifecycle_pub_sub_launch.py at master · ros2/launch · GitHub
- launchファイルの例(シンプルなやつ)
- demos/add_two_ints.launch.py at master · ros2/demos · GitHub
- Launchシステムのドキュメントは作成中(議論中)
The launch documentation
launchの詳細なドキュメントはここ
launch/architecture.rst at master · ros2/launch · GitHub
上に挙げた2つのlaunchファイルと見比べながら読み解きます
Launch Entities and Launch Descriptions
- launchのメインオブジェクトは
launch.LaunchDescriptionEntity
- 2つ目の例でreturnしてるやつ
- launchの仕方、非同期なイベントへの対応をここに記述する
visit
という、起動中(launching)に呼ばれるメソッドを持つdescribe
という、振る舞いを考える際(introspection)に呼ばれるメソッドを持つasyncio.Future
というメソッドを、非同期な活動を持ったときに返す
- visitしたとき、次にvisitされる実体(entities)が生成される
launch.LaunchDescription
はlaunch.Action
の集合を持つ- actionsはイベント対応時に実行される
- launch descirptionとactionsは
launch.Substituion
への参照をもつ - launch descriptionとactionsは、直接実行されるだけでなく
launch.LaunchService
から起動(launch)することもできる- launch serviceはイベントループを操作したり、actionsを発行したりする
Actions
- Actionsはプロセスを実行したり、設定値をセットしたりできる
- Actionsは追加のactionsを生成できる
- これはシンタックスシュガーなactionsを作るときに使用される
- Actionsは引数を持つ
- 引数は
launch.Substitution
が与えることができる
- 引数は
Basic Actions
- 10個以上のactionsがある(ここでは上記の2例で使われているactionsをピックアップする)
launch.actions.RegisterEventHandler
はlaunch.EventHandler
を登録するEventHandler
はユーザが定義できるラムダ式で、イベントを対処する- イベントは1つのイベントだったり、イベントの集合だったりする
- 1つ目の例では
event_handlers.OnStateTransition
が登録されている - 2つ目の例では
event_handlers.OnProcessExit
が登録されている
launch.actions.LogInfo
はユーザが定義したメッセージのログを取る- LogInfo以外にもLogWarmもある(たぶんLogDebugもあると思う)
- 1つ目の例で使っている
launch.actions.EmitEvent
はlaunch.Event
を発行する- 1つ目の例では
events.lifecycle.ChangeState
を発行している - 2つ目の例では
events.Shutdown
を発行している
- 1つ目の例では
Substitutions
- 上記2例では使われていないので読み飛ばす
The Launch Service
- 主に3つのサービスを担う
- launch description を組み込む(include)
- イベントループを回す
- シャットダウンする
- 動いてるactionsとevent handlersを止める
- イベントループを止める
- 代表的な使用例は
- launch descriptionを作るとき(プログラムやマークアップファイルによる生成)
- launch serviceを作るとき
- launch descriptionをlaunch serviceに組み込むとき
- shutdown時に呼ばれるsignal handlerを追加するとき
- launch service のイベントループを回すとき
- 1つ目の例でlaunch serviceが使われている
- 最初にデフォルトのlaunch descriptionを読み込んでいる
- 次に、複数のactionsを追加したlaunch descriptionを読み込んでいる
- 最後に
return ls.run()
でイベントループを回している
- 対照的に、2つ目の例ではlaunch serviceが使われていない
return launch.LaunchDescription
を使用している
- 別スレッドでサーバを立てると動的にlaunch serviceを読み込めるみたいだけど、よくわからない
Event Handlers
- Event ahndlersは主に2つのメソッドを定義する
launch.EventHandler.matches
はeventを入力し、eventが適合するとTrueを返すlaunch.EventHandler.handle
はeventとlaunch contextを入力し、launch.LaunchDescriptionEntity
のリストを返す(任意)
拡張するときのポイント
- 新しいactions
launch.Action
クラスを直接/間接的に継承すること
- 新しいevents
launch.Event
クラスを直接/間接的に継承すること
- 新しいsubstitutions
launch.Substitution
クラスを直接/間接的に継承すること
- launch descriptionの実体(entities)
launch.LaunchDescriptionEntity
クラスを直接/間接的に継承すること
- 将来的にはsetuptoolsのentry_pointみたいな拡張方法が使えるようになるみたい
Launchファイルを実装する
読んでるだけだと理解できないので、 前回作成したPub/Subノード用のlaunchファイルを作成します。
作成したパッケージがこちらです。
各クラス・メソッドについてのドキュメントが見つからなかったので、 ソースコードを読みながらTimerAction機能を追加しました。
GitHub - ros2/launch: Tools for launching ROS nodes.
パッケージディレクトリ
- launch、cpp、pythonを同じ階層にしたかったが実現できなかった
- launchをcpp(first_cpp_pkg)の一部として組み込むことで解決した
first_ros2_pkg ├── README.md ├── cpp │ ├── CMakeLists.txt │ ├── first_sub_node.cpp │ ├── launch │ │ └── pub_sub.launch.py │ └── package.xml └── python ├── first_pub_node.py ├── package.xml ├── setup.cfg └── setup.py
launchファイル(pub_sub.launch.py)
from launch import LaunchDescription import launch.actions import launch_ros.actions def generate_launch_description(): return LaunchDescription([ # Launch時にメッセージを出力 launch.actions.LogInfo( msg="launch publisher node and subscriber node"), # Launchしてから5秒後にメッセージを出力 launch.actions.TimerAction(period=5.0,actions=[ launch.actions.LogInfo( msg="----------This is a TimerAction!!!---------"), ]), # Publisherノードを起動 # first_pub_nodeは別パッケージのノードだが、問題なく起動する launch_ros.actions.Node( package='first_python_pkg', node_executable='first_pub_node', output='screen'), # Subscriberノードを起動 launch_ros.actions.Node( package='first_cpp_pkg', node_executable='first_sub_node', output='screen'), ])
CMakeLists.txt
~~~中略~~~ add_executable(first_sub_node first_sub_node.cpp) ament_target_dependencies(first_sub_node rclcpp std_msgs) install(TARGETS first_sub_node DESTINATION lib/${PROJECT_NAME}) # launchディレクトリをshareへインストールする記述を追加 install(DIRECTORY launch DESTINATION share/${PROJECT_NAME}) ament_package()
package.xml
~~~中略~~~ <exec_depend>rclcpp</exec_depend> <exec_depend>std_msgs</exec_depend> <!-- 追加 --> <exec_depend>launch_ros</exec_depend> <export> <build_type>ament_cmake</build_type> </export> </package>
実行結果
$ ros2 launch first_cpp_pkg pub_sub.launch.py [INFO] [launch.user]: launch publisher node and subscriber node # LogInfoが働いている [INFO] [launch]: process[first_pub_node-1]: started with pid [828] [INFO] [launch]: process[first_sub_node-2]: started with pid [829] [INFO] [first_pub_node]: Publishing: "Hello World! ^^: 0" [INFO] [first_sub_node]: I heard: 'Hello World! ^^: 0' [INFO] [first_pub_node]: Publishing: "Hello World! ^^: 1" [INFO] [first_sub_node]: I heard: 'Hello World! ^^: 1' [INFO] [first_pub_node]: Publishing: "Hello World! ^^: 2" [INFO] [first_sub_node]: I heard: 'Hello World! ^^: 2' [INFO] [first_pub_node]: Publishing: "Hello World! ^^: 3" [INFO] [first_sub_node]: I heard: 'Hello World! ^^: 3' [INFO] [first_pub_node]: Publishing: "Hello World! ^^: 4" [INFO] [first_sub_node]: I heard: 'Hello World! ^^: 4' [INFO] [first_pub_node]: Publishing: "Hello World! ^^: 5" [INFO] [first_sub_node]: I heard: 'Hello World! ^^: 5' [INFO] [first_pub_node]: Publishing: "Hello World! ^^: 6" [INFO] [first_sub_node]: I heard: 'Hello World! ^^: 6' [INFO] [first_pub_node]: Publishing: "Hello World! ^^: 7" [INFO] [first_sub_node]: I heard: 'Hello World! ^^: 7' [INFO] [first_pub_node]: Publishing: "Hello World! ^^: 8" [INFO] [first_sub_node]: I heard: 'Hello World! ^^: 8' [INFO] [launch.user]: ----------This is a TimerAction!!!--------- # TimerActionを使ってlaunchしてから5秒後にメッセージを出力している [INFO] [first_pub_node]: Publishing: "Hello World! ^^: 9" [INFO] [first_sub_node]: I heard: 'Hello World! ^^: 9' [INFO] [first_pub_node]: Publishing: "Hello World! ^^: 10" [INFO] [first_sub_node]: I heard: 'Hello World! ^^: 10'
PublisherとSubscriberが起動したこと、 LogInfoが機能したこと、 TimerActionが機能したことが確認できました。
次のステップ
次回はROS 2 TutorialsのWorking with multiple RMW implementationsからやっていきます。