ロボットのプログラムは、カメラを読む、地図を作る、障害物を避ける、車輪を回す、といった異なる周期・異なる失敗モードを持つ仕事の集合である。これを一つの巨大なプロセスに詰め込むと、変更も再利用も障害切り分けも難しくなる。ROS 2(Robot Operating System 2)は、これらを独立した部品として実装し、メッセージを通じて接続するためのミドルウェアと開発規約の集合である。OSそのものではない。Linux、Windows、macOSの上で動き、複数コンピュータや複数ロボットにまたがる分散システムを作れる。

ROS 1の使いやすい通信モデルを引き継ぎつつ、ROS 2はDDSを基盤に採用し、発見、QoS、セキュリティ、組込み・実時間用途をより正面から扱えるよう設計された。本稿では「どの部品が、いつ、どの品質で、どの座標系の情報を渡すか」という観点でROS 2を捉える。

0. 30秒要約

1. まず全体像をつかむ

ROS 2では、実行中のプログラムをノード(node)と呼ぶ。例えばcamera_driverは画像を出し、detectorは画像から物体を検出し、localizationは自己位置を出し、controllerは車輪に速度を命令する。それぞれは単体でテスト・交換・再起動できる。ノードをまとめた配布単位がパッケージ、複数ノードの起動構成がlaunchファイルである。

ROS 2移動ロボットの構成 センサー・ドライバscan / image / imu ros2_control状態取得・モータ出力 位置推定 / TF2map → odom → base_link Nav2計画・回避・行動管理 アプリケーション目標指示・監視・UI トピック cmd_vel odom / joint_states DDS:発見、配送、QoS、必要に応じて暗号化を担当Linux / RTOS・ネットワーク・実機ハードウェア

図1 — センサー、位置推定、ナビゲーション、制御は別ノードとして動く。矢印は代表的なデータ流であり、DDSはその配送を、TF2は座標関係を担う。

ここで重要なのは「分割そのもの」が目的ではないことだ。センサー取得は高頻度で遅延を嫌い、経路計画は多少遅くても良く、UIは切断しても車輪を止めるべきである。この異なる要求を、境界と契約(メッセージ型、名前、QoS、座標系、周期)として明文化することがROS 2を使う価値になる。

2. DDSとROS graph

ROS 2のクライアントライブラリrclcpp(C++)とrclpy(Python)は、下位のrclを経由してDDS実装を利用する。DDS(Data Distribution Service)はpublish/subscribe型のデータ配送標準で、参加者が同一のROS domain内で互いを発見する仕組みを持つ。ROS 1のように必須の中央masterへ全ノードが接続する構成ではないため、複数機へ広げやすく、ネットワーク断や再参加への設計自由度も高い。一方で、マルチキャスト、VPN、Wi-Fi、Docker、異なるDDS実装の相互運用では発見が最初の障害になりやすい。

ROS_DOMAIN_IDは発見範囲を論理的に分ける番号である。同じ研究室や会場に複数ロボットがあるとき、意図しないトピック接続を避けるために設定する。ただしDomain IDはアクセス制御ではない。アクセス制御は後述のSROS2/DDS Securityで別に行う。RMW(ROS Middleware)層によりDDS実装を差し替えられ、Fast DDS、Cyclone DDSなどを用途と運用条件に応じて選べる。

ROS graphは、ノード、トピック、サービス、アクション、パラメータ、名前空間から成る実行時の関係図である。ros2 node listros2 topic listrqt_graphで観測できる。実装前に「誰が所有し、誰が発行し、誰が購読し、どのフレームで、どの頻度か」を表にしておくと、後からの統合不良が大きく減る。

3. 通信モデルを使い分ける

ノードとトピック

ノードは名前空間付きの実行主体、トピックは型付きの非同期ストリームである。LiDARの/scan、カメラの/image_raw、速度指令の/cmd_velが典型例になる。publisherはデータを発行して先へ進み、subscriberは到着時にコールバックを受ける。画像やセンサーのように「最新値が次々来る」「購読者がいなくても発行側の仕事は進めたい」データにはトピックが合う。

メッセージは.msgで定義する。型は通信契約なので、意味を曖昧にしない。例えば速度の単位はSI、姿勢や位置にはheader.stampheader.frame_idを持たせる。独自メッセージを作る前にgeometry_msgssensor_msgsnav_msgsstd_msgsなど既存型を調べるのがよい。似た概念に異なる型を乱立させると、後からエコシステムに接続できなくなる。

サービス

サービスはrequest/replyの一回の要求応答である。/reset_odometry、キャリブレーション値の取得、短時間で完了するモード変更などに向く。サービス呼び出しを制御ループの中で待つ設計は危険である。応答不能、サーバ再起動、ネットワーク遅延を前提にタイムアウトと代替動作を持たせる。

アクション

アクションは「時間がかかり、途中の進捗と取消を必要とする仕事」のためのプロトコルである。goal、feedback、result、cancelから成り、内部では複数トピックとサービスを組み合わせる。NavigateToPoseのような移動目標、マニピュレータの軌道実行、探索タスクに適する。UIが「目的地へ移動」を送信し、進捗を受け、危険時にcancelする、という人間に分かりやすい操作にもなる。

手段 時間的性質 典型例 避けるべき使い方
トピック 連続・非同期 /scan/odom/cmd_vel 完了確認が必要な一回限りの命令
サービス 短い要求応答 リセット、設定取得 長い処理、周期ループ内の同期待ち
アクション 長時間・取消・進捗あり 移動、軌道実行 数百Hzのセンサー配送

4. TF2:座標系の契約を守る

ロボットは「障害物がx=2」という値だけでは動けない。センサー座標系なのか、ロボット中心なのか、地図座標系なのかで意味が変わる。TF2は座標フレーム間の剛体変換を、時刻付きの木として配布・検索する仕組みである。base_linkを機体基準、lasercamera_linkをセンサー、odomを連続性を重視した局所座標、mapを大域的な補正を含む座標として置くのが移動ロボットでの典型である。

map → odom → base_link → laserという木では、自己位置推定がmap → odomを、車輪オドメトリがodom → base_linkを、URDFとstatic transformがbase_link → laserを出す。1つのchild frameに親は1つだけ、同じ変換を複数ノードが競合して発行しない、タイムスタンプを実測時刻に合わせる、という三点が重要だ。過去時刻のセンサー値を変換するには、その時刻のTFがバッファに残っていなければならない。遅延を無視して「現在の変換」を使うと、走行中には見かけ上の位置ずれになる。

5. QoS:届くことと、新しいことは別の要件

DDSのQoS(Quality of Service)は、同じトピックでも配送契約を選べる仕組みである。publisherとsubscriberの要求が互換でなければ接続しない場合があるため、デバッグ時は型だけでなくQoSを見る。特にセンサー用のSensorDataQoSは低遅延を優先する既定値であることが多く、rosbagや可視化ツールが別のQoSを要求すると「topic listには見えるのに受からない」状態になる。

ポリシー 主な選択 設計上の意味
Reliability reliable / best_effort 全配送を優先するか、欠落より遅延を避けるか
Durability volatile / transient_local 後から来た購読者へ最後の値を渡すか
History / depth keep last Nなど バースト時に何件を待機させるか
Deadline 期待周期 期限を越えた更新欠落を検出する手掛かり
Lifespan 有効期間 古くなった命令・観測を配送しない

緊急停止や状態遷移のような「落としてはいけない」通知は、信頼性だけで安全とは言えない。通信が切れたことを検出し、watchdogが安全側へ倒す設計が必要である。反対に高帯域画像をreliableかつ深いキューにすると、損失時に古い画像が蓄積し、追跡が過去を見続ける。制御に渡すなら最新値優先の方が正しいことが多い。

6. Executor、Callback Group、ライフサイクル

subscriber、timer、service、actionの処理本体はコールバックである。Executorは待機可能なイベントを取り出してこれを実行する。SingleThreadedExecutorは実行順が追いやすく、MultiThreadedExecutorは独立した重い処理を並行できる。しかしスレッドを増やすだけでは周期遅れは直らず、共有状態の競合、優先度逆転、キャッシュ競合を増やすこともある。Callback Groupをmutually exclusiveまたはreentrantに設定し、センサー処理、計画、I/Oがどこまで同時に走ってよいかを先に決める。

ライフサイクルノードは、unconfigured → inactive → active → finalizedという状態を明示する。configureでパラメータとメモリを確保し、activateでpublishや制御を開始し、deactivateで外部への作用を止め、cleanupで資源を解放する。地図、センサー、モータの準備が整う前にナビゲーションを始めない、異常時に速度出力だけ確実に止める、といった起動順序を表現できる。大きなシステムではlaunchとlifecycle managerで依存順を管理する。

7. 最小の実装例:温度を発行して購読する

次はPythonでstd_msgs/msg/Stringを1秒ごとに発行する最小例である。実務では数値に適した型とタイムスタンプを使うが、ノード、publisher、timer、subscriber、Executorの関係を掴むには十分である。

# talker.py
import rclpy
from rclpy.node import Node
from std_msgs.msg import String

class Talker(Node):
    def __init__(self):
        super().__init__('talker')
        self.pub = self.create_publisher(String, 'chatter', 10)
        self.count = 0
        self.create_timer(1.0, self.publish)

    def publish(self):
        msg = String()
        msg.data = f'hello {self.count}'
        self.pub.publish(msg)
        self.get_logger().info(msg.data)
        self.count += 1

def main():
    rclpy.init()
    node = Talker()
    rclpy.spin(node)       # 既定Executorがtimerを実行する
    node.destroy_node()
    rclpy.shutdown()
# listener.py
import rclpy
from rclpy.node import Node
from std_msgs.msg import String

class Listener(Node):
    def __init__(self):
        super().__init__('listener')
        self.create_subscription(String, 'chatter', self.on_msg, 10)

    def on_msg(self, msg):
        self.get_logger().info(f'received: {msg.data}')

def main():
    rclpy.init()
    node = Listener()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

パッケージを作った後、動作の観察・切り分けにはCLIを使う。次の例で、グラフが期待通りか、メッセージ型・レート・QoSが一致しているかを確認できる。

# ワークスペースをビルド済みとして環境を読み込む
source install/setup.bash
ros2 run my_pkg talker
ros2 run my_pkg listener

ros2 node list
ros2 topic list -t
ros2 topic echo /chatter
ros2 topic hz /chatter
ros2 topic info /chatter --verbose
ros2 interface show std_msgs/msg/String
ros2 param list /talker
ros2 doctor --report

ros2 topic echoが通らないときは、名前空間、ROS_DOMAIN_ID、型、QoS、ネットワーク発見の順に確認する。ログだけでなく、ros2 bag recordで入出力を記録し、同じ時刻列を再生して再現性のあるデバッグに持ち込むのが有効である。ただしbagの再生時刻(use_sim_time/clock)を実機時刻と混同しない。

8. Nav2とros2_controlへつなぐ

Nav2はROS 2向けのナビゲーションスタックで、地図、コストマップ、経路計画、局所回避、行動木(Behavior Tree)による復旧を組み合わせる。利用側は通常NavigateToPoseアクションへ目標姿勢を送る。Nav2が機体へ出す代表的な出力はgeometry_msgs/Twist/cmd_velである。Nav2に必要なのは少なくとも、ロボット形状、センサー障害物情報、map → odom → base_linkのTF、オドメトリ、そして速度指令を受けて動く底盤である。

ros2_controlは、この最後の「実機をどう読む/動かすか」をハードウェア非依存の形にする枠組みである。URDF内のros2_control記述で関節のcommand interface(位置、速度、努力)とstate interfaceを宣言し、hardware interface pluginがCAN、EtherCAT、シリアル、GPIOなど実機の通信を実装する。controller managerがライフサイクルを管理し、diff_drive_controllerならcmd_velを左右車輪の速度へ変換し、エンコーダからjoint_statesodomを返す。

流れは、目標姿勢 → Nav2 action → /cmd_vel → diff_drive_controller → hardware interface → モータ、そしてエンコーダ → /odom/TF → Nav2、となる。ここでcmd_velの座標系、最大加減速、timeout、非常停止の責務を曖昧にしない。Nav2の出力を直接モータへ渡すのではなく、制限・watchdog・ハードウェア故障検出を制御層に置く。マニピュレータでは同じ位置にMoveIt 2、joint_trajectory_controller、FollowJointTrajectory actionが入る。

9. リアルタイム性:ROS 2が保証するもの、しないもの

DDSやQoSがあるからといって、締切が必ず守られるわけではない。エンドツーエンド遅延は、センサー取得、シリアライズ、DDS配送、Executor待ち、コールバック実行、制御出力の合計で決まる。汎用Linuxのスケジューラ、ページフォールト、動的メモリ確保、GC、ログI/O、Wi-Fiの再送は大きなジッタを作る。ROS 2は実時間設計を可能にする部品だが、実時間保証そのものではない。

厳しい制御では、周期ループを小さく固定し、割当てを起動時に済ませ、コールバック内のブロッキングI/Oとファイル出力を避ける。実時間カーネル(PREEMPT_RT)、CPU affinity、スレッド優先度、DDSの設定、loaned messageやintra-process通信を検討する。高レベルの経路計画と低レベルの電流・トルク制御を同じExecutorへ置かないことも多い。最終的には平均レートでなく、期限超過、最大遅延、CPU負荷、通信断時の停止時間を計測する。安全停止の経路は、複雑な認識・計画系に依存しない設計が望ましい。

10. セキュリティ:ロボットのトピックは内部専用ではない

同一ネットワークの参加者を自動発見する便利さは、意図しない参加者にも届きうるということでもある。特に/cmd_vel、地図、カメラ映像、診断情報は、機密性・完全性・可用性を別々に考える必要がある。ROS 2ではSROS2を通じてDDS Securityの認証、暗号化、アクセス制御を利用できる。鍵と証明書を作り、各ノード/enclaveに「publish可能なtopic」「subscribe可能なtopic」「service/actionの呼出権限」を最小限で与える。

ただし暗号化はCPU負荷と鍵運用を伴い、証明書失効や時刻ずれで起動不能になることもある。開発用に全面許可した設定を実機へ持ち込まない。ネットワーク分離、ファイアウォール、VPN、コンテナの権限分離、署名済み更新、ログからの秘密情報除去、非常停止の独立系統も必要である。セキュリティはDDSのオプション一つで終わる話ではなく、機体が物理的に作用することを前提にした運用設計である。

11. ディストリビューションの選び方

ROS 2は定期的にディストリビューション(distro)を出す。2026年9月時点では、長期保守とUbuntu LTSとの組み合わせを重視するならJazzy Jalisco、新しい機能を検証するならKilted Kaiju、次期版の開発追従ならRolling Ridleyが判断の出発点になる。実際のサポート期限、対応OS、パッケージ可用性は変動するため、導入前に必ず公式の配布一覧と各リリースノートを確認する。公式情報はROS 2 ReleasesJazzy JaliscoKilted Kaijuにある。

本番機は「最新」ではなく、必要なドライバ、Nav2、ros2_control、GPU・OS・カーネル、社内の保守期間が揃う組合せを固定する。Rollingを量産機の基盤に使うのは、意図して追従試験を継続できる場合に限るのが安全である。複数distroの混在はメッセージ定義や依存関係の差を増やすので、原則としてコンテナやネットワーク境界で分ける。

12. 最近の研究・開発の方向性

ROS 2そのものはアルゴリズムではなく、研究成果を実機へ統合するための土台でもある。現在の方向性の一つは、学習ベースの知覚・行動と古典的な安全制約の結合である。視覚言語モデルや模倣学習は曖昧な指示への一般化を伸ばすが、推論遅延、失敗の説明可能性、閉ループでの安全保証は別問題として残る。そのため、高レベルの意味理解を学習器、局所衝突回避・速度制限・緊急停止を検証可能な層へ分ける構成が増えている。

二つ目は、シミュレーションから実機への移行を速くするデジタルツインとHardware-in-the-Loopである。URDF、ros2_controlのhardware interface、同一のcontroller設定を共有し、実機固有の遅延・飽和・センサー雑音を再現する。完全なシミュレータを目指すより、失敗しやすい境界条件を回帰テストに固定する考え方が重要になる。

三つ目は、分散・複数ロボットの観測と協調である。DDSは多参加者を支えるが、無線帯域、時刻同期、名前空間、権限、部分切断を設計しなければ台数を増やすだけで不安定になる。地図の共有、タスク割当て、フリート管理では、ローカル制御を機体側に閉じ、クラウドや中央系が止まっても安全に退避できる自律性が求められる。

最後は、実時間性・安全性を測定可能にするトレーシングと検証である。メッセージの到達時刻、Executorの待ち、CPUスケジューリング、制御周期を観測し、再現できるbagとシナリオで評価する。性能を「速そう」ではなく、最悪遅延・欠落率・停止距離という要求へ落とし込む姿勢が、研究コードをロボット製品へ近付ける。

13. 設計判断の比較表

論点 選択肢A 選択肢B 判断の軸
実装言語 rclcpp rclpy 低遅延・制御寄りはC++、試作・統合はPythonが扱いやすい
通信 トピック サービス/アクション 連続ストリームか、応答/取消/進捗が必要か
配送 best effort reliable 新鮮さ優先か、欠落許容度が低いか
実行 SingleThreaded MultiThreaded Executor 再現性優先か、独立処理の並列性が必要か
状態管理 通常ノード Lifecycle node 起動順・停止保証・再構成が重要か
座標 TF2を使う 数値だけを渡す 複数フレーム/時刻を扱うならTF2が必須
走行 自作スタック Nav2 + ros2_control 標準的な移動機能を早く統合するなら後者
防御 閉域だけに依存 SROS2 + ネットワーク防御 人・資産・映像に影響する実機では多層防御

14. 実装を始める前のチェックリスト

ROS 2を学ぶ近道は、最初から大規模な自律ロボットを作ることではない。小さなpublisher/subscriberでグラフを観察し、次にTFとQoSを含むセンサー処理を作り、最後にNav2とros2_controlで実機の閉ループへ接続する。この順序なら、問題が通信、座標、時間、制御、安全のどこにあるかを切り分けながら、拡張可能なロボットソフトウェアへ育てられる。

#ROS 2 #DDS #Nav2 #ros2_control #ロボティクス入門