Featured image of post ROS2の基本

ROS2の基本

目次

背景

  • 業務で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.pyament_pythonというビルドタイプを使う)

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の例
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import rclpy
from rclpy.node import Node
from std_msgs.msg import String

class MinimalPublisher(Node):
    def __init__(self):
        super().__init__('minimal_publisher')
        self.publisher_ = self.create_publisher(String, 'topic', 10)
        self.timer = self.create_timer(0.5, self.timer_callback)
        self.count = 0

    def timer_callback(self):
        msg = String()
        msg.data = f'Hello World: {self.count}'
        self.publisher_.publish(msg)
        self.get_logger().info(f'Publishing: "{msg.data}"')
        self.count += 1

def main(args=None):
    rclpy.init(args=args)
    node = MinimalPublisher()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()
  • ポイント
    • 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_velrobot2/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: 終了処理が終わった状態
  • 状態遷移は、configureactivatedeactivatecleanupshutdownといったサービス呼び出しで制御する
  • 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ファイル>: 記録内容(トピック・件数・期間など)を確認する
  • 保存形式はsqlite3mcapが使われる

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つ
  • 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を使えば、実機なしでもロボットのシミュレーションができる
Built with Hugo
テーマ StackJimmy によって設計されています。