はじめに
「ロボットアームを自分で動かしてみたい」──そんな思いから、オープンソースのロボットアーム OpenArm と ROS2 に挑戦してみました。
私自身、ロボット開発は初心者です。実機はまだ持っていませんが、ROS2の「mock hardware(仮想ハードウェア)」機能を使えば、PCだけでロボットアームのシミュレーションができます。この記事では、OpenArmをROS2上でシミュレーション起動し、MoveItで動かした動きをrosbagに記録、後から再生してRViz2で確認する、という一連の流れを紹介します。
一見シンプルに思える「記録と再生」ですが、初心者にとってはハマりポイントがいくつもありました。同じ道を辿る方の参考になれば幸いです。 <!– 📸 キャプチャ①【ゴール画像】rosbag再生中にRViz2でロボットが動いている画面。記事の顔になるのでここは必ず撮影 –>
OpenArmとは?
OpenArmは、東京に拠点を置く Enactic社 が開発した、完全オープンソースのヒューマノイドロボットアームです。
主なスペックは以下の通りです。
- 7自由度(7DOF):人間の腕に近い動きが可能
- ペイロード4.1kg(ピーク6.0kg)
- リーチ633mm:人間の腕とほぼ同じスケール
- 高いバックドライバビリティ:外力に対して柔軟に追従するので、人との協働作業に向いている
- 完全オープンソース:CADデータ、ファームウェア、制御ソフトウェアすべてが公開されている
最大の魅力は価格です。双腕セット(2本のアーム + グリッパー)で 約$5,000〜$6,500。研究用ロボットアームは数百万円するものが多い中、100万円以下で双腕ロボットが手に入るのは驚きです。
Enactic社はOpenArmを使って介護施設や家庭での物理的なタスクを自動化するヒューマノイド「Ena」の開発を進めています。LeRobotエコシステムとの連携も公式にサポートされており、模倣学習やテレオペレーションのプラットフォームとしても注目されています。

ROS2とは?
ROS2(Robot Operating System 2) は、ロボットのソフトウェアを開発するための通信フレームワークです。「OS」と名前についていますが、WindowsやLinuxのようなOSではなく、ロボットの各機能をつなぐ「共通言語」のようなものです。

ROS2では、ロボットの各機能(モーター制御、センサー処理、経路計画など)を 「ノード」 という独立したプログラムに分割します。ノード同士は 「トピック」 というチャネルを通じてメッセージをやり取りします。
この記事では ROS2 Humble(Ubuntu 22.04向けの安定版)を使用しています。
知っておくと読みやすくなる用語集
URDF ── ロボットの「設計図」
URDF(Unified Robot Description Format) は、ロボットの構造をXMLで記述したファイルです。人間の体に例えると以下のようになります。
- Link(リンク):骨のようなもの。ロボットの剛体パーツで「上腕」「前腕」「手」などに相当します
- Joint(ジョイント):関節。リンクとリンクをつなぐ可動部で「肩」「肘」「手首」に相当します
メッシュファイル(STL / DAE)── ロボットの「見た目」
URDFが「骨組みの設計図」だとすると、メッシュファイルはその骨に「肉付け」するための3Dモデルデータです。
- STL:3Dプリンターでもおなじみのフォーマット。色情報なし、形状のみ
- DAE(COLLADA):色やテクスチャ情報も含む、より表現力のあるフォーマット
RViz2でロボットの「見た目」が表示されるのは、URDFがこれらのメッシュファイルを参照しているからです。
TF(Transform)── 各パーツの「位置と姿勢」
ロボットの各リンクが3D空間のどこに、どの向きで存在するかを表す座標変換です。「肘の角度が30度」という情報があれば、「前腕はこの位置・この向きにある」というTFが計算されます。
rosbag ── トピックの「録画・再生」
ROS2のトピックに流れるメッセージを、タイムスタンプ付きで丸ごとファイルに保存する機能です。ビデオ録画のROS版と思ってください。後から再生すれば、録画時とまったく同じメッセージが同じタイミングで配信されます。
source コマンドについて
この記事では、新しいターミナルを開くたびに以下のコマンドを実行します。
source ~/ros2_ws/install/setup.bash
これは「ワークスペースの環境を読み込む」コマンドです。ROS2では、自分でビルドしたパッケージの場所をシステムに教える必要があります。sourceを実行しないと「package not found」エラーになります。
毎回打つのが面倒であれば ~/.bashrc に追記すればターミナル起動時に自動で読み込まれますが、conda等の他のツールと競合する場合があるので、今回は手動で実行する方針にしています。
環境
| 項目 | 内容 |
|---|---|
| OS | Ubuntu 22.04 |
| ROS2 | Humble Hawksbill |
| GPU | NVIDIA GPU 4080 |
| 実機 | なし(mock hardwareで作業) |
ROS2 Humbleのインストールは完了している前提です。公式ドキュメントに従って ros-humble-desktop をインストールしてください。
全体の流れ
今回の作業は大きく4ステップです。
ステップ1: OpenArmのワークスペースを構築する
↓
ステップ2: シミュレーションでOpenArmを起動する(bringup)
↓
ステップ3: MoveItで動かして、動きをrosbagに記録する
↓
ステップ4: 記録した動きをrosbagで再生し、RViz2で確認する
ステップ1:ワークスペースの構築
リポジトリの取得とビルド
# ワークスペースの作成
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws/src
# OpenArmのメインリポジトリをクローン
git clone https://github.com/enactic/openarm_ros2
# 依存パッケージ(URDF定義やCANドライバ)を自動取得
vcs import < ./openarm_ros2/openarm.repos
# ビルド(実機用ハードウェアインターフェースは今回不要なので除外)
cd ~/ros2_ws
colcon build --packages-ignore openarm_hardware
追加パッケージのインストール
MoveIt2やコントローラ関連のパッケージもインストールしておきます。
sudo apt install -y \
ros-humble-controller-manager \
ros-humble-gripper-controllers \
ros-humble-hardware-interface \
ros-humble-joint-state-broadcaster \
ros-humble-joint-trajectory-controller \
ros-humble-ros2controlcli \
ros-humble-moveit \
ros-humble-joint-state-publisher-gui
ステップ2:シミュレーションでOpenArmを起動する
双腕モードでの起動
ターミナル1を開きます。
source ~/ros2_ws/install/setup.bash
ros2 launch openarm_bringup openarm.bimanual.launch.py use_fake_hardware:=true
use_fake_hardware:=true がポイントで、これにより実際のハードウェアがなくても仮想的にロボットが起動します。RViz2が立ち上がり、双腕のロボットが表示されます。
bringup は「ロボットの体を起動する」コマンドです。これにより以下が同時に立ち上がります。
- robot_state_publisher:URDFを読み込み、関節角度からTFを計算して配信する係
- ros2_control_node:コントローラを管理する係
- RViz2:3D表示ウィンドウ
- 各種コントローラ:関節の値を100Hzで管理・配信する係

ステップ3:MoveItで動かしてrosbagに記録する
MoveItとは
MoveIt2はロボットの「頭脳」にあたるフレームワークです。「この手をあの位置に動かしたい」と指示すると、障害物を避けながら各関節の軌道を自動計算してくれます。
bringupが「体」、MoveItが「頭脳」。両方同時に起動する必要があります。
MoveItの起動
ターミナル2を新しく開きます。
source ~/ros2_ws/install/setup.bash
ros2 launch openarm_bimanual_moveit_config demo.launch.py
⚠️
sourceを忘れるとpackage not foundエラーになります。新しいターミナルを開くたびに必ず実行してください。
MoveItのRViz2が開きます。bringup側のRViz2は×で閉じてOKです(プロセスはバックグラウンドで動き続けます)。

MoveItでの操作方法
MoveItのRViz2にはアームの先端に インタラクティブマーカー(赤い輪や矢印)が表示されています。
- マーカーをドラッグすると、ゴースト(半透明のアーム) が目標ポーズに移動します。この時点ではまだロボットは動いていません
- 画面左下の Planning タブにボタンが3つあります
- Plan:目標ポーズまでの軌道を計算し、プレビューアニメーションを表示(ロボットは動かない)
- Execute:計算済みの軌道を実行して実際にロボットを動かす
- Plan & Execute(P&E):上の2つを一気に実行。サクッとやりたい時はこちら
- P&Eを押すと、実体(不透明なアーム)がゴーストの位置まで動きます。これが「ロボットが動いた」状態です
💡 Planボタンで見えるアニメーションは「こう動きますよ」というプレビューです。実際にロボットは動いていないので、rosbagに記録されません。P&EまたはExecuteを押して初めて
/joint_statesの値が変わります。
rosbagで記録する
P&Eでロボットが動くことを確認できたら、記録を始めます。ターミナル3を新しく開きます。
source ~/ros2_ws/install/setup.bash
ros2 bag record /joint_states -o ~/my_bag
記録が始まったら、MoveItのRViz2に戻ってP&Eで何回か動かしてみてください。いろいろなポーズで動かすと、再生した時に面白くなります。
動かし終わったら、ターミナル3でCtrl+Cを押して記録を停止します。
記録の確認
動きが正しく記録されているか確認します。まずターミナル1(bringup)とターミナル2(MoveIt)をCtrl+Cで全部止めてから、以下を実行します。
ros2 bag info ~/my_bag
メッセージ数や記録時間が表示されます。次に、関節の値が0以外になっているか確認します。
ターミナルA:
ros2 bag play ~/my_bag
ターミナルB(再生開始から5秒後くらいに):
ros2 topic echo /joint_states --once | grep -A 16 position
0以外の値が含まれていれば、動きが正しく記録されています。全部0.0の場合は、P&Eを押さずに記録を止めてしまった可能性があるので、やり直してください。
ステップ4:rosbagを再生してRViz2で確認する
ここがこの記事の本題です。
再生用のlaunchファイルを作成する(初回のみ)
まず、bringupから双腕のURDFを取得します。
ターミナル1:
source ~/ros2_ws/install/setup.bash
ros2 launch openarm_bringup openarm.bimanual.launch.py use_fake_hardware:=true
ターミナル2:
ros2 topic echo /robot_description --once --field data > ~/ros2_ws/bimanual.urdf
URDFが保存できたら、ターミナル1のbringupをCtrl+Cで止めます。
次に、再生用のlaunchファイルを作成します。
cat << 'EOF' > ~/ros2_ws/play_rosbag.launch.py
from launch import LaunchDescription
from launch_ros.actions import Node
import os
def generate_launch_description():
urdf_path = os.path.expanduser('~/ros2_ws/bimanual.urdf')
with open(urdf_path, 'r') as f:
urdf = f.read()
return LaunchDescription([
Node(
package='robot_state_publisher',
executable='robot_state_publisher',
parameters=[{'robot_description': urdf, 'use_sim_time': True}],
),
Node(
package='rviz2',
executable='rviz2',
parameters=[{'use_sim_time': True}],
),
])
EOF
このlaunchファイルは以下の2つのノードを起動します。
- robot_state_publisher:URDFと関節角度からTFを計算して配信する係。
use_sim_time: Trueで、rosbagの時刻に合わせて動作します - RViz2:3D表示ウィンドウ。同じく
use_sim_time: Trueで時刻を合わせます
再生手順
ターミナル1:launchファイルを起動
source ~/ros2_ws/install/setup.bash
ros2 launch ~/ros2_ws/play_rosbag.launch.py
RViz2が開いたら、以下を設定します。
- 左パネルの Fixed Frame →
worldに変更 - 左下の Add ボタン → RobotModel を追加
- RobotModelの Description Source を File に変更
- Description File にURDFのフルパスを入力(例:
/home/ユーザー名/ros2_ws/bimanual.urdf)
⚠️ Description Sourceがデフォルトの「Topic」のままだとメッシュが表示されない場合があります。「File」に変更してURDFのパスを直接指定するのが確実です。

ターミナル2:rosbag再生
ros2 bag play ~/my_bag --clock
--clock オプションは、rosbagの録画時刻をシミュレーション時計として配信するオプションです。これがないと、RViz2はrosbagのデータを「古すぎるデータ」として無視してしまいます(詳しくは後述のハマりポイント参照)。
RViz2でロボットが動けば成功です!
データの流れ(最終構成)
再生時のデータの流れをまとめます。bringup(コントローラ込み)は使わず、必要最小限のノードだけで構成しています。
rosbag play --clock
│
├─▶ /joint_states(関節角度データ)
│ │
│ ▼
│ robot_state_publisher(use_sim_time: True)
│ │
│ ├─▶ /tf(各リンクの位置・姿勢)
│ └─▶ /robot_description(URDF)
│
└─▶ /clock(シミュレーション時刻)
│
▼
RViz2(use_sim_time: True)
│
└─▶ /tf + URDF + メッシュ → 3D表示!
ハマったポイントまとめ
ここまでスムーズに書きましたが、実際にはかなりハマりました。同じ場所でハマらないよう、まとめておきます。
① bringup起動中にrosbag再生しても動かない
症状:bringupを起動したまま ros2 bag play しても、RViz2のロボットが動かない(一瞬動いてすぐ戻る)。
原因:bringupのコントローラ(joint_state_broadcaster)が100Hzで /joint_states を上書きし続けている。rosbagが別の値を配信しても、すぐにコントローラの値で上書きされてしまう。
解決策:再生時はbringupを使わず、robot_state_publisherだけの軽量構成にする(本記事のステップ4の方法)。
② rosbag再生してもRViz2で動かない(タイムスタンプ問題)
症状:robot_state_publisherだけの構成にしても、RViz2でロボットがまったく動かない。
原因:rosbagに記録されたメッセージには録画時のタイムスタンプが付いている。再生時の現在時刻とは大きくずれているため、RViz2が「古すぎるデータ」として無視してしまう。
解決策:ros2 bag play に --clock オプションを付けて、全ノードで use_sim_time: True を設定する。これにより全ノードがrosbagの時刻に合わせて動作するようになる。
③ RViz2にロボットの3Dモデルが表示されない
症状:TF(座標軸の矢印)は見えるが、ロボットの3Dモデル(メッシュ)が表示されない。
原因:RViz2のRobotModelがURDFを正しく受け取れていない。通信品質(QoS)の設定が合っていないか、タイミングの問題。
解決策:RobotModelの Description Source を「Topic」から「File」に変更し、URDFファイルのパスを直接指定する。
④ rosbagに動きが記録されていない
症状:再生しても関節の値がすべて0.0。
原因:MoveItでP&E(Plan & Execute)を実行せずに記録を止めてしまった。ゴーストを動かしただけ、またはPlanボタン(プレビューのみ)を押しただけでは /joint_states の値は変わらない。
解決策:記録中に必ず P&E または Execute を押して、実体のアームを動かす。記録後に ros2 topic echo で0以外の値があるか確認する。
⑤ source を忘れて「package not found」
症状:ros2 launch ... で「package not found」エラー。
原因:新しいターミナルで source ~/ros2_ws/install/setup.bash を実行していない。
解決策:新しいターミナルを開くたびに必ず実行する。または ~/.bashrc に追記して自動化する(ただし他ツールとの競合に注意)。
学んだこと
「一番シンプルな構成」から始めるのが近道
最初はbringup(コントローラ込み)の上でrosbag再生しようとして苦戦しましたが、最終的にはrobot_state_publisherだけというミニマルな構成で解決しました。複雑な構成でうまくいかないときは、必要最小限のノードだけで試すのが近道です。
デバッグは「データの流れ」を追う
ROS2の世界では、各機能がノードとして独立して動いています。問題が起きたときは「どのノードが、どのトピックに、何を配信しているか」を一つずつ確認していくのが基本です。よく使ったコマンドをまとめておきます。
# トピックにデータが流れているか確認
ros2 topic echo /joint_states --once | grep -A 16 position
# トピックの配信レートを確認
ros2 topic hz /tf
# パラメータの確認
ros2 param get /robot_state_publisher use_sim_time
タイムスタンプは想像以上に重要
ROS2のメッセージにはすべてタイムスタンプが付いています。ノード間でこの時刻がずれていると、データが正しくても「古い」と判断されて無視されます。rosbag再生時の use_sim_time + --clock は、この時刻を合わせるための仕組みです。
おわりに
「rosbagで記録して再生するだけ」が、こんなに奥深いとは思いませんでした。コントローラの上書き、タイムスタンプのずれ、メッシュの表示問題──小さなハマりポイントが積み重なって、シンプルなゴールに到達するのに苦労しました。
でも、ようやくロボットが画面の中で動いた瞬間は、素直に嬉しかったです。同じようにROS2で初めてロボットを動かそうとしている方の参考になれば幸いです。
