目次
背景
- 業務でROS2を使う必要が出てきた
- 自分向けにROS2の基本的な考え方と用語を整理する
ROS2とは
- Robot Operating System 2の略だが、OS(オペレーティングシステム)そのものではない
- ロボット向けのソフトウェアを作るためのミドルウェア兼フレームワーク
- 「センサーで取得した値を処理して、モーターに指令を送る」といった、ロボットの各機能をバラバラのプログラム(後述のノード)として作り、それらを通信でつなげて1つのロボットとして動かす、という考え方が中心にある
- 前身のROS1に対して、以下のような改善が入っている
- 通信の基盤にDDSという業界標準の仕組みを採用し、単一障害点(後述)をなくした
- リアルタイム性や、Windows・組み込み環境への対応が強化された
- セキュリティ機能が追加された
ディストリビューションとインストール
- ROS2は、毎年1回コードネームつきのバージョン(ディストリビューション)がリリースされる
- 全てのバージョンがLTS(長期サポート)というわけではなく、偶数年リリースのものがLTS(サポート期間5年)、奇数年リリースのものは短期サポート(サポート期間約1年半)になっている
- 主なLTS版
- Humble Hawksbill(2022年、Ubuntu 22.04向け)
- Jazzy Jalisco(2024年、Ubuntu 24.04向け)
- 迷ったら、その時点の最新LTS版を、対応するUbuntuのバージョンに入れるのが基本
- ROS1と違い、ROS2はOSのバージョンとの結びつきが強いため、Ubuntuのバージョンを先に決めてから、対応するROS2のバージョンを選ぶことになる
全体アーキテクチャ
DDS(Data Distribution Service)
- ROS2の通信を裏で支えているミドルウェアの規格
- 「マスターレス」という特徴があり、ROS1にあった
roscoreのような、通信を仲介する中央サーバーが存在しない - 各ノードが自律的にお互いを見つけて(Discoveryと呼ぶ)、直接データをやり取りする
- 中央サーバーがないため、その1台が落ちるとシステム全体が止まる、という単一障害点の問題が起きにくい
ノード(Node)
- ROS2における処理の最小単位
- 1つのノードが、1つのプログラム(プロセス)に対応する
- 例えば「カメラ画像を取得するノード」「物体検出をするノード」「モーターを制御するノード」のように、機能ごとにノードを分けて作る
- ノード同士は、後述のTopic・Service・Actionという3つの方式で通信する
ノード間の通信
ノード同士の通信には、目的に応じて3つの方式がある。
| 方式 | 通信の形 | 用途の例 |
|---|---|---|
| Topic | 非同期のpub/sub | センサーの値を流し続ける |
| Service | 同期的なリクエスト/レスポンス | 一度だけの問い合わせ・設定変更 |
| Action | 非同期+進捗報告+キャンセル可能 | 時間のかかる動作(アーム移動など) |
Topic(トピック)
- 特定の名前(トピック名)に対して、データを送り続ける側(Publisher)と、受け取り続ける側(Subscriber)が非同期に繋がる仕組み
- PublisherとSubscriberはお互いの存在を意識しなくてよく、同じトピック名にさえ繋がれば、何対何でも通信できる
- センサーの値のように、継続的に流れてくるデータに向いている
Service(サービス)
- 関数呼び出しのように、リクエストを送って、その場でレスポンスを待つ同期的な通信
- 「今の設定値を教えて」「このパラメータを変更して」のような、1回のやり取りで完結する処理に向いている
- レスポンスが返るまで基本的に待つことになるので、時間のかかる処理には向かない
Action(アクション)
- Serviceに、進捗報告(フィードバック)とキャンセル機能を足したような仕組み
- 「アームを目標位置まで動かす」のように、時間がかかり、途中経過を知りたかったり、途中で中断したくなったりする処理に向いている
- 内部的にはTopicとServiceの組み合わせで実現されている
メッセージ型・インターフェース
- Topic・Service・Actionでやり取りするデータの型(インターフェース)は、それぞれ
.msg・.srv・.actionという専用のファイル形式で定義される - 標準で用意されている型は、目的別のパッケージにまとまっている
std_msgs: Int32・String・Boolなど、基本的な型geometry_msgs: Twist(速度)・Pose(位置・姿勢)など、幾何情報の型sensor_msgs: Image・LaserScan・PointCloud2など、センサーデータの型
- 標準の型で足りない場合は、自分のパッケージ内に
.msgファイルなどを作って独自の型を定義できる ros2 interface show <型名>で、その型がどんなフィールドを持つか確認できる
cmd_vel(速度指令)
- ロボットを動かすための速度指令に、事実上の標準として使われているトピック名
geometry_msgs/Twist(またはTwistStamped)という、並進速度(linear)と回転速度(angular)を持つメッセージ型を使う- Nav2の出力や、キーボード操作用のteleopノードなど、様々なノードがこの
/cmd_velにpublishし、ロボット側のモーター制御ノードがそれをsubscribeして実際に動かす、という流れが一般的 - 複数のノードが同時に
/cmd_velを出そうとすると競合するため、優先順位をつけて1つの指令にまとめるtwist_muxのようなノードがよく使われる
QoS(Quality of Service)
- DDSが持つ、通信の品質を細かく設定できる仕組み
- ROS1にはなかった、ROS2特有の重要な概念
- 代表的な設定項目
- Reliability: 通信の信頼性。多少データが欠けてもいいから速さを優先する(Best Effort)か、確実に届ける(Reliable)か
- Durability: 新しく繋がったSubscriberに対して、過去のデータも渡すかどうか
- History: 直近何件分のデータを保持しておくか
- PublisherとSubscriberでQoSの設定が噛み合っていないと、通信自体が繋がらないことがあるため、初心者がハマりやすいポイントの1つ
開発の基本的な流れ
ワークスペース
- ROS2のプロジェクト全体を置く作業ディレクトリ
- 中に
src/ディレクトリを作り、その下に複数のパッケージ(後述)を置く構成が基本 colcon buildというビルドツールで、ワークスペース内の全パッケージをまとめてビルドする
パッケージ
- ROS2における、機能をまとめる単位(1つ以上のノードやライブラリをまとめたもの)
- 各パッケージには
package.xml(パッケージの情報・依存関係を書くファイル)が必須 - ビルド方式によって、以下のように使うファイルが変わる
- C++の場合:
CMakeLists.txt - Pythonの場合:
setup.py(ament_pythonというビルドタイプを使う)
- C++の場合:
rosdepとワークスペースのsource
- パッケージが依存する外部ライブラリを、OSのパッケージマネージャ経由で解決してくれるツールが
rosdep - ワークスペースの
src/以下にある全パッケージの依存関係を、rosdep install --from-paths src -y --ignore-srcのようなコマンドでまとめて解決できる colcon buildでビルドした後は、source install/setup.bashを実行しないと、ビルドしたパッケージがシェルから認識されない- 「ビルドは通ったのに
ros2 runできない」というのは、大抵このsourceのし忘れが原因で、初心者が最初につまずきやすいポイントの1つ
基本的なコマンド
ros2 run <パッケージ名> <実行ファイル名>: 1つのノードを実行するros2 launch <パッケージ名> <launchファイル名>: 複数のノードをまとめて起動するros2 node list: 今動いているノードの一覧を見るros2 topic list/ros2 topic echo <トピック名>: トピックの一覧確認・流れているデータの確認ros2 service list: サービスの一覧を見るrviz2: センサーの値やロボットの状態を可視化するツールrqt: ノードの構成やログをGUIで確認できるツール
最小限のコード例(rclpy)
- ここまでは概念の説明だったので、実際に最小限のノードを書いてみる
- 以下は、Pythonのクライアントライブラリ
rclpyを使った、シンプルなPublisherの例
| |
- ポイント
Nodeを継承したクラスを作るのが基本形create_publisherでPublisherを作り、create_timerで一定間隔ごとにtimer_callbackを呼ぶrclpy.spin(node)で、ノードを動かし続ける(この中で、後述のExecutorがコールバックを回している)
- Subscriberの場合は
create_subscriptionを使い、メッセージが届くたびに呼ばれるコールバック関数を登録する形になる
Executor
- ノード内の複数のコールバック(タイマー・Subscriberなど)を、実際にどう実行するかを管理する仕組み
rclpy.spin(node)を呼ぶと、内部でデフォルトのExecutor(SingleThreadedExecutor)が動き、登録されたコールバックを順番に処理する- シングルスレッドのExecutorだと、1つのコールバックの処理が終わるまで次のコールバックは実行されない
- 時間のかかる処理を1つのコールバックに書いてしまうと、他のコールバック(他のトピックの受信など)がその間ブロックされてしまう
- 複数のコールバックを並行して処理したい場合は、MultiThreadedExecutorを使う
- 「トピックが来ているはずなのに反応が遅い/来ない」という不具合の原因が、実はExecutorのブロッキングだった、というのはよくあるハマりどころ
実行時の設定と制御
パラメータ(Parameters)
- ノードに渡す設定値のこと。プログラムを書き換えずに、動作を外から調整できる仕組み
- 例えば「カメラのフレームレート」「しきい値」のような値を、パラメータとして持たせることが多い
- コマンドでの確認・操作
ros2 param list: あるノードが持つパラメータの一覧を見るros2 param get <ノード名> <パラメータ名>: 値を取得するros2 param set <ノード名> <パラメータ名> <値>: 値を変更する
- 実際の運用では、パラメータを1つずつ
setするのではなく、後述のLaunchファイルからYAMLファイルでまとめて渡すのが一般的
Namespace・Remapping
- Namespaceは、ノード名やトピック名の前につける「名前空間」で、同じパッケージ・同じノードを複数のインスタンスとして区別して動かすときに使う
- 例:
robot1/cmd_vel、robot2/cmd_velのように、ロボットごとに名前空間を分ける
- 例:
- Remappingは、ノードが元々使っているトピック名・サービス名などを、実行時に別の名前に付け替える仕組み
- 例:
ros2 run pkg node --ros-args -r cmd_vel:=robot1/cmd_vel
- 例:
- どちらも、コードを一切変更せずに、起動時のオプションやLaunchファイルの引数だけで名前を切り替えられるのがポイント
- 複数台のロボットを同時に動かす場合や、他人が作ったパッケージをそのまま自分のシステムに組み込む場合に、ほぼ必須になる知識
Launchファイル
- 複数のノードを、パラメータ付きでまとめて起動するための設定ファイル
- Python・XML・YAMLのいずれかで書ける(最近はPythonで書くことが多い)
- 実際のロボットは複数のノードが同時に動いて成り立っているため、1つずつ
ros2 runするのではなく、launchファイルでまとめて立ち上げるのが基本的な運用になる
ライフサイクルノード(Managed Nodes)
- 通常のノードは起動したらすぐ全機能が有効になるが、ライフサイクルノードは明示的な状態管理を持つ
- 主な状態
- Unconfigured: 起動直後、まだ設定されていない状態
- Inactive: 設定は終わっているが、まだ動いていない状態
- Active: 実際に処理を行っている状態
- Finalized: 終了処理が終わった状態
- 状態遷移は、
configure・activate・deactivate・cleanup・shutdownといったサービス呼び出しで制御する - Nav2は、内部の各サーバー(Planner、Controllerなど)がこのライフサイクルノードとして作られていて、
lifecycle_managerというノードが、それらの状態をまとめて管理・遷移させている - Nav2起動時に「なぜか動かない」場合、実はライフサイクルが
Activeまで遷移していないだけ、ということもよくある
ロボット特有の周辺技術
rosbag2
- トピックに流れているデータを記録・再生するツール(ROS1の
rosbagのROS2版) - 実機やシミュレーションで流れたデータをファイルに記録しておき、後から同じデータを何度でも再生できる
- 実機を毎回動かさなくても、記録済みのデータでノードの開発・デバッグを進められるのが大きな利点
- 主なコマンド
ros2 bag record -a: 全トピックを記録するros2 bag record <topic1> <topic2>: 指定したトピックだけ記録するros2 bag play <bagファイル>: 記録したデータを再生するros2 bag info <bagファイル>: 記録内容(トピック・件数・期間など)を確認する
- 保存形式は
sqlite3かmcapが使われる
URDF(Unified Robot Description Format)
- ロボットの物理的な構造(リンクとジョイント)を記述するXML形式
- リンク(Link)は剛体の部品(腕の一部、車体など)、ジョイント(Joint)はリンク同士をつなぐ関節(回転・スライドなど)を表す
- 各リンクの見た目(形状・色)や、衝突判定用の形状、質量・慣性などの物理情報もここに書ける
robot_state_publisherというノードが、URDFとジョイントの角度情報から、後述のTF2の座標変換を自動的に計算・配信してくれる- RVizでロボットの3Dモデルを表示したり、Gazeboでシミュレーションしたりする際の元データにもなる
TF2(座標変換)
- ロボットに関わる複数の座標系(フレーム)の関係を、時系列で管理する仕組み
- 例えば「地図(map)」「ロボット本体(base_link)」「カメラ(camera_link)」は、それぞれ別の座標系を持っていて、TF2はこれらの間の相対位置・姿勢を管理する
- 各フレームは木構造(ツリー)でつながっていて、
map -> odom -> base_link -> camera_linkのように親子関係を辿ることで、任意の2点間の座標変換を計算できる ros2 run rqt_tf_tree rqt_tf_treeで、今のフレーム構成をツリー図として確認できる- Nav2のようなナビゲーションスタックは、TF2が正しく配信されていることが前提になっているため、ロボット開発で最初につまずきやすいポイントの1つ
Nav2(Navigation2)
- ROS2上で自律移動(ナビゲーション)を実現するための標準的なソフトウェアスタック
- 地図上で目的地を指定すると、経路計画と障害物回避をしながら自律的に移動できるようにする
- 主な構成要素
- Global Planner: 地図全体を使って、大まかな経路を計画する
- Controller(Local Planner): 直近の障害物やロボットの動特性を考慮して、実際の速度指令(前述の
cmd_vel)を計算する - Costmap: 障害物や走行コストを表現する地図。全体用のGlobal Costmapと、直近用のLocal Costmapの2種類がある
- Behavior Tree: 経路計画→移動→リカバリー動作といった、Nav2全体の動作フローを制御する仕組み
- Recovery Behaviors: 経路が見つからない・スタックしたときに行う復帰動作(その場回転、後退など)
- Nav2自体は「自分が今どこにいるか」は推定しないため、AMCLなどの自己位置推定と組み合わせて使うのが前提
- launchファイルで各構成要素をまとめて立ち上げ、パラメータYAMLで細かい挙動をチューニングするのが基本的な使い方
Gazebo(シミュレータ)
- ロボットの物理シミュレーションを行うためのソフトウェア
- 前述のURDFで記述したロボットモデルを仮想空間に配置し、重力・摩擦・衝突などを考慮した動きをシミュレートできる
- 実機がまだない、あるいは実機を壊すリスクがある動作を試したいときに、ROS2のノードをそのまま実機と同じように動かして検証できる
- カメラ・LiDARなどのセンサーのシミュレーションデータも出力できるので、Nav2のようなセンサー入力が前提のスタックのテストにもよく使われる
- 最近は従来のGazebo Classicから、後継のGazebo(旧Ignition Gazebo)への移行が進んでいる
まとめ
- ROS2は、ロボットの各機能をノードという単位に分けて、通信でつなげて動かすためのフレームワーク
- インストールは、その時点の最新LTSディストリビューションを、対応するUbuntuのバージョンに入れるのが基本
- 通信の基盤はDDSで、ROS1にあった中央サーバー(単一障害点)がない
- ノード間の通信は、Topic(非同期の流し込み)・Service(同期的な問い合わせ)・Action(進捗報告つきの長時間処理)の3種類を目的に応じて使い分ける。QoSはその通信の品質を細かく設定する仕組み
- 実際のコードは、
Nodeを継承したクラスにPublisher/Subscriberやコールバックを持たせる形で書き、rclpy.spinが内部のExecutorでコールバックを回す - 開発は、ワークスペースの中にパッケージを作り、rosdepで依存関係を解決してcolconでビルドし、setup.bashをsourceして使う、という流れが基本
- 動作の調整はパラメータで行い、Namespace・Remappingで名前を実行時に切り替えられる。複数ノードとパラメータをまとめて起動するのがLaunchファイルの役割
- rosbag2でデータを記録・再生できるので、実機を毎回動かさなくても開発・デバッグを進められる
- URDFでロボットの構造を記述し、そこからTF2が座標変換を計算・配信する。この2つはNav2などの前提になる基礎的な仕組み
- 速度指令は
cmd_velという標準的なトピック名でやり取りされ、ライフサイクルノードで状態管理されたNav2が、そのcmd_velを出す自律移動用の標準スタック - Gazeboを使えば、実機なしでもロボットのシミュレーションができる
