#07 で、Wi-Fi の具合によって /scan の届き方が変わることを確かめました。それに、ノードを少し直すたびにロボットを置き直し、電源を入れて試していては、時間がいくらあっても足りません。
そこでこの回は、ロボットのデータを一度だけ記録し、そのあとはロボットにつながずに記録を再生してノードを作ります。記録と再生には rosbag(ros2 bag コマンド)を使います。
- ロボットの周りに箱を置き、30 秒ほど記録する(実験 1)
- 記録に何が入っているかを読む(実験 2)
- ロボットから切り離し、記録を RViz で再生する(実験 3)
- 記録を使って、前方の障害物までの距離を表示するノードを作る(課題)
この回も、ロボットは動かしません。
どの実験も、コマンドを打つ前に「どうなるか」の予想をノートに書いてください。何を予想すればよいかは、次の表のとおりです。各実験の結果の画面と説明は、「結果を見る」をクリックすると開きます。予想を書き、自分で実行してから開いてください。
| 実験 | 予想すること |
|---|---|
| 2. 記録の中身 | 30 秒ほど記録したとき、/scan はおよそ何件入っているか。#07 で測った /scan の周期から計算する |
| 3. ロボットなしで再生 | ロボットから切り離しても、RViz に点が出るか。ros2 topic hz /scan は記録していたときと同じくらいになるか |
| 課題 | 自分のノードの「前」の値は、scan_monitor の「前」と同じくらいになるか。範囲や正面の角度を変えると、値はどちらへ変わるか |
予想は外れてかまいません。点になるのは、予想と結果が違ったときに「なぜ違ったか」を考えて書くところです。分からないときも空欄にせず、「分からない」と書いて、手がかりにしたことを 1 つ書きます。実験 1 は記録をとる作業なので、予想はいりません。
授業で受ける人は、この回の記録を実習レポート(Word)の「第 8 回」のシートに書きます。スクリーンショットは、シートの中の「画面:」の欄(実験や記録の表のすぐ下)に貼ります。ひとりで学ぶ人も、予想と結果を同じ形でノートに残しておくと、あとで見返せます。
この回で撮っておく画面
動画は撮りません。そのかわり、下の画面を撮って残します。授業で受ける人は、実習レポートの「第 8 回」の、それぞれの「画面:」の欄に貼ります。ひとりで学ぶ人も、同じ画面を残しておくと、あとで値を見直せます。最初の 1 枚は、実習を始める前に端末で TZ=Asia/Tokyo date と打ち、日付と時刻が出た画面です。授業の日に実習したことの記録になります。講義用の環境の時計は世界標準時(UTC)なので、TZ=Asia/Tokyo を付けて日本時間で出します。
TZ=Asia/Tokyo dateと、つないだ直後のrobot-statusros2 bag info(記録時間と件数)- 記録を再生しているときの
ros2 topic hz /scan - 自分のノードの出力(
ros2 run front_check front_nearestの端末) scan_monitorと自分のノードを並べた画面(同じ場面の 2 つの値)- 再生を一時停止したときの、自分のノードの表示
この回で使うもの
| もの | 使うところ |
|---|---|
| 講義用の環境 | すべて |
| ロボットと囲い、箱 2〜3 個 | 実験 1 |
| 設計シート(#04 で使ったもの) | 課題 |
| #09 で保存した RViz の設定(あれば) | 実験 3 |
実験 1 30 秒記録する
この実験は、次の順に進めます。
- ロボットを囲いの中の床に置き、周りに箱を並べる(ロボットは歩かせない)
- ロボットにつなぎ、
robot-statusで確かめる - 記録用のフォルダ
~/bagsを作って移動する ros2 bag recordで 30 秒ほど記録する。途中で箱を 1 つ動かす
ロボットを囲いの中の床に置き、周りに箱を並べます。ロボットにつないだら、robot-status でブリッジが動作中かを確かめ、その画面を回の頭の 1 枚として撮ります。続けて、記録用のフォルダを作って移動します(mkdir -p と && は #09 の実験 3 と同じです)。ros2 bag record は、コマンドを打った場所に記録のフォルダを作るので、先に移動しておきます。
robot-connect
robot-status
mkdir -p ~/bags && cd ~/bags
robot-connect は、IP を付けないと前回つないだ IP につなぎます。OLED の 2 行目の IP が前回と変わっていたら(ホットスポットを立て直したときなど)、robot-connect 192.168.137.134 のように IP を付けて打ちます。
次のコマンドで記録を始めます。30 秒ほどたったら Ctrl+C で止めます。記録している途中で、箱を 1 つだけ動かしてください。動かした時刻をメモしておきます。箱を動かすのは、あとで自分のノードを確かめるときに「場面が変わったら値も変わるか」を見るためです。動かすときは、ロボットの脚や LiDAR に手や箱を当てないようにします。
ros2 bag record -o room_scan /scan /tf /tf_static /joint_states
| 書いたもの | 意味 |
|---|---|
-o room_scan | 記録を room_scan という名前のフォルダに保存する |
/scan | LiDAR の距離データ |
/tf と /tf_static | 座標系の関係(#09)。これが無いと、再生したときに点をロボットのどこに置けばよいかわからない |
/joint_states | 脚の関節の角度 |
記録できるのは、ここに並べたトピックだけです。あとで「あれも記録しておけばよかった」とならないように、何に使うかを考えてから選びます。
実験 2 記録に何が入っているかを読む
ロボットから切り離してから、記録の中身を表示します。
robot-disconnect
ros2 bag info room_scan
ros2 bag info は、記録したファイルを読むだけなので、ロボットにつながっていなくても使えます。見るところは、記録した時間の Duration と、下の Topic information の各行にある Count(そのトピックのメッセージが何件入っているか)です。
考える:/scan は 1 秒に何回届いていたか
Duration と、/scan の Count から、記録していた間の /scan の平均の周波数を計算してください。#07 の実験 3 で ros2 topic hz で測った値と合っているでしょうか。/tf も同じように計算してみます。
計算の例(自分で計算してから開く)
/scan:160 ÷ 32.27 秒 ≒ 4.96 Hz。#07 で確かめた、ロボット側のブリッジで 5 Hz に間引いた値とほぼ同じです。
/tf:318 ÷ 32.27 秒 ≒ 9.85 Hz。/tf はブリッジで 10 Hz までにしているので、これもほぼ上限どおりです。
記録の周波数が上限より大きく低いときは、記録していた間に Wi-Fi でデータが落ちていたことになります。記録にも、そのときの通信の具合がそのまま残ります。
実験 3 ロボットなしで再生する
この実験は、次の順に進めます。
- ロボットから切り離されていることを確かめる
- 端末 1 で記録をくり返し再生する
- 端末 2 で RViz を起動し、再生した点を表示する
- 端末 3 で
ros2 topic hz /scanを測り、実験 2 で計算した周期と比べる
再生する前に、robot-disconnect でロボットから切り離しておきます。つないだままだと、ロボットから届く今の /scan と、記録から再生した /scan が同じ名前で混ざってしまいます。実験 2 で切り離していれば、そのままで大丈夫です。心配なときは robot-status で確かめます。
端末 1 で記録を再生します。--loop は、最後まで再生したら最初に戻ってくり返す指定です。
# 端末 1
cd ~/bags
ros2 bag play room_scan --loop
端末 2 で RViz を起動し、#09 の実験 4 と同じように Fixed Frame・TF・LaserScan を設定します(#09 で設定を保存した人は rviz2 -d ファイル名)。RobotModel は、記録に /robot_description を入れていないので表示できません。追加しないか、追加してあればチェックを外します。
# 端末 2
rviz2
端末 3 で、再生している /scan の周期を測ります。数行出たら Ctrl+C で止め、average rate を書きます。
# 端末 3
ros2 topic hz /scan
結果を見る(予想を書き、自分で実行してから開く)
記録の途中で箱を動かした場面では、点の並びが変わります。--loop でくり返すので、同じ場面を何度でも見られます。
端末 3 の average rate は、記録していたときとほぼ同じになります(実験 2 で計算した周期)。ros2 bag play は、記録したときの時刻の間隔をそのまま再現して送るからです。
端末 1 でスペース キーを押すと再生を一時停止でき、もう一度押すと続きから再生します。課題の検証で使います。
課題 前方でいちばん近い物体までの距離を表示するノード
記録を使って、次のノードを作ります。
ロボットの前方 ±30° の範囲で、いちばん近い物体までの距離を、1 秒に 1 回表示する。
#04 と同じく、自分で決めるのは「何を作るか」と「できたものが決めたとおりか」です。コードは AI に書かせてかまいません。次の順に進めます。
/scanの 1 回分の中身を読む- 設計シートを書く(分からない欄は、AI に聞き返してもらいながら自分で決める)
- AI にコードを書かせ、設計シートと突き合わせてからパッケージに入れる
- 記録を再生しながら動かし、設計シートの「検証の方法」で確かめる
- サンプルの
scan_monitorと同じ場面で比べる
/scan の中身を確かめる
設計する前に、/scan の 1 回分の中身を見ておきます。端末 1 で記録を再生したまま、端末 3(実験 3 で ros2 topic hz を測った端末)で次を実行します。端末 2 の RViz は開いたままにしておきます。
ros2 topic echo --once /scan
$ ros2 topic echo --once /scan
(ranges と intensities の数字の列は省いた)
header:
stamp:
sec: 1789480703
nanosec: 15327125
frame_id: laser_frame
angle_min: 0.0033161682076752186
angle_max: 6.274807453155518
angle_increment: 0.013936647213995457
time_increment: 0.00022498289763461798
scan_time: 0.1012423038482666
range_min: 0.05000000074505806
range_max: 20.0
| 項目 | この LiDAR の値 | 意味 |
|---|---|---|
angle_min | 0.0033 rad(1 回ごとに少し変わる) | 1 つめの点の角度 |
angle_max | 6.2748 rad | 最後の点の角度。0 から 2π(360°)近くまで 1 周している |
angle_increment | 0.01394 rad(約 0.8°) | となりの点との角度の差 |
ranges | 約 450 個の数 | それぞれの角度で測った距離 [m] |
range_min・range_max | 0.05 m・20.0 m | この範囲の外の値は、測れていない点として扱う |
i 番目の点(0 から数える)の角度は angle_min + i × angle_increment です。角度は laser_frame の x 軸を 0 として、上から見て反時計回り(左回り)に増えます。
設計シートを書く
#04 と同じ形の設計シート(#04 の「1. 設計シートを書く」の白紙)を使います。#04 の counter や even_printer とちがって、このノードには新しく決めることがいくつかあります。次の表の欄は必ず書いてください。「何を書くか」と「なぜ要るか」を読んでから、自分の値を決めます。#04 の白紙に無い「前方の判定」と「測れていない点」の 2 つは、白紙の「座標系」の下に「■ 前方の判定」「■ 測れていない点」と見出しを自分で足して書きます。
| 欄 | 何を書くか | なぜ要るか・手がかり |
|---|---|---|
| 受け取るもの(入力) | トピック名・型・QoS・期待する周期 | 型は ros2 topic info -v /scan で見られます。QoS は #09 の実験 2 の「考える」で、センサーのデータをどちらで受けるとよいかを考えました。周期は、実験 2 で計算した値です。QoS を送り手と合わない組み合わせにすると、エラーも出ずに何も届きません(#09 の実験 1 の A) |
| 座標系と「前方」の決め方 | どの座標系の、何度を正面とするか | /scan の角度は laser_frame の x 軸が 0 です(#09 の実験 6 の frame_id)。#09 の実験 5 で、LiDAR の 0° がロボットの正面と合っていたかを確かめました。その結果をここに書きます。ずれていたら、正面を何度として扱うかを書きます |
| 範囲に入るかの判定 | ±30° に入る点を、角度からどう選ぶか | 上で読んだとおり、この LiDAR の角度は 0〜2π で 1 周します。「前方 ±30°」が角度の数字でどこに当たるかを書きます。下のヒントに、つまずきやすいところをまとめています |
| 測れていない点の扱い | inf・nan・range_min 未満・range_max を超える値をどうするか。範囲に 1 つも有効な点が無いときに何を表示するか | inf は「限りなく大きい(何も返ってこなかった)」、nan は「数になっていない」という値です。これをそのまま「いちばん近い」の計算に入れると、答えがおかしくなります。上の表の range_min・range_max の意味を使って決めます |
| 出すもの(出力) | 1 秒に 1 回、何をどんな形で表示するか | ロボットに指令は出さないので、画面に表示するだけでかまいません。白紙の「出すもの」の表は、トピック名の欄に「なし(画面に表示)」と書き、何を表示するかを型の欄の代わりに書きます。scan_monitor と比べやすいよう、m 単位で小数 2 桁くらいにしておくと見比べやすくなります |
| 止まる条件 | 不要 | 速度指令を出さないので要りません(#04 と同じ)。ただし、第 5 週でこのノードを「止まる」ノードに育てるときは必ず書きます |
| 検証の方法 | 記録のどの場面で、どんな値になれば正しいと言えるか | 実験 1 で箱を動かした時刻をメモしました。その前後で値がどう変わるはずかを書きます。scan_monitor の「前」との比べ方も書いておきます |
記入例:受け取るものの欄だけ(自分で書いてから開く)
■ 受け取るもの
トピック名 | 型 | QoS | 期待する周期
-----------|-----------------------------|-------------|-------------
/scan | sensor_msgs/msg/LaserScan | best effort | 5 Hz
QoS を best effort にしたのは、#09 の実験 1 の C のとおり、送り手が reliable でも best effort でも受け取れるからです。周期の 5 Hz は、実験 2 で記録の件数と時間から計算した値です。ほかの欄は、自分の実験 5 の結果によって変わるので、例は載せません。
分からない欄は、AI に聞き返してもらう
#04 と同じく、空欄のまま AI に「書いて」と頼んではいけません。AI に質問役をしてもらい、1 欄ずつ自分で答えて埋めます。#04 のプロンプトを、このノード用に書きかえたものが次です。/scan について自分で調べた結果と、書けたところまでのシートを貼って渡します。
ROS 2 のノードの設計シートを書いています。分からない欄があるので、手伝ってください。
次のルールを守ってください。
・答えを先に書かないでください。空欄を 1 つずつ取り上げて、私に質問してください。
・私が「分からない」と答えたら、その欄が何を決めるものかを高校生にも分かる言葉で説明し、選ぶための手がかりを出してから、もう一度聞いてください。
・私の答えがおかしいと思ったら、どこがおかしいかを言って聞き返してください。
・全部の欄が埋まったら、シート全体を書き出してください。
作るノード:LiDAR の /scan を受け取り、ロボットの前方 ±30° の範囲でいちばん近い物体までの距離を、1 秒に 1 回表示するノード(ROS 2 Humble・Python)
/scan について私が調べたこと:
(ここに ros2 topic info -v /scan の結果と、ros2 topic echo --once /scan で見た angle_min・angle_max・angle_increment・range_min・range_max を貼る)
私が書いたところまでの設計シート:
(ここに書きかけのシートを貼る)
とくに「前方」の欄は、AI には分かりません。自分のロボットの LiDAR がどちらを向いていたかは、#09 の実験 5 で自分が見た結果だけが根拠です。AI が「0° が正面です」と言っても、そのまま書かずに自分の記録と見比べてください。
ヒント:±30° の判定でつまずきやすいところ
この LiDAR の角度は 0 から 2π まで増えます。正面を 0° とすると、前方 ±30° に入るのは「0°〜30°」と「330°〜360°」の 2 か所に分かれます。「角度の絶対値が 30° 以下」とだけ書くと、330°〜360° の点(右前)が抜け落ちます。
角度の差を −180°〜180° の範囲に直してから比べると、1 つの式で判定できます。設計シートに、どちらの方法をとるかを書いておきます。
AI に作らせて、パッケージに入れる
パッケージの作り方とビルドは #04 と同じです。パッケージ名とファイル名は、ここでは front_check と front_nearest にします(自分で決めた名前でもかまいません。そのときは、下のコマンドとプロンプトの名前も合わせて変えます)。/scan の型 LaserScan は sensor_msgs というパッケージに入っているので、--dependencies に sensor_msgs を入れます。パッケージの作成からビルド、ノードの起動までは、端末 3 で続けて行います。
cd ~/ros2_ws/src
ros2 pkg create --build-type ament_python --node-name front_nearest front_check --dependencies rclpy sensor_msgs
AI には、次のプロンプトの最後に自分の設計シートを貼って渡します。
次の設計シートどおりに、ROS 2 Humble で動く Python(rclpy)のノードを書いてください。
パッケージ名は front_check、ファイルは front_check/front_nearest.py です。
設計シートにないトピックやパラメータは足さないでください。
(ここに自分の設計シートを貼る)
返ってきたコードは、パッケージに入れる前に設計シートと突き合わせます。見るところは次の 4 つです。説明できない行があったら、AI に「この行は何をしていますか」と聞き返します。
create_subscription:型がLaserScan、トピック名がscan(/scanと書いても同じトピックになる)、QoS がシートと同じか- 角度の計算:
angle_min + i * angle_incrementで角度を出し、シートに書いた「前方」の判定をしているか inf・nan・範囲外の値を、シートどおりに取り除いているかcreate_timer:1 秒ごとに表示しているか
突き合わせたら、#04 の「3-3」と同じようにひな形の front_nearest.py を開いて中身を置き換え、Ctrl+S で保存してからビルドします。
codium ~/ros2_ws/src/front_check/front_check/front_nearest.py
cd ~/ros2_ws && colcon build --symlink-install && source install/setup.bash
記録で検証する
- 端末 1 で
ros2 bag play room_scan --loopを動かしたまま、ビルドしてsourceした端末 3 でros2 run front_check front_nearestを実行して自分のノードを起動する。 - 箱を動かした場面で、表示される距離が変わるかを確かめる。RViz でその場面の点と見比べる。
- 端末 1 でスペース キーを押して再生を一時停止し、自分のノードの表示がどうなるかを見る。止まったままの値を出し続けるのか、何も出なくなるのかを書く。
- わざと範囲を ±90° にしたり、正面を 180° にしたりして、値が予想どおりに変わるかも試す。コードの値を書きかえて保存し、端末 3 のノードを Ctrl+C で止めて、上の
colcon buildの行を打ち直してから起動し直す。試し終えたら、設計シートの値に戻す。 - 自分のノードを動かしたまま、新しい端末で
ros2 topic info -v /scanを打つ。受ける側(Subscription)に自分のノードが出ていて、その Reliability が設計シートの QoS と同じかを見る。
4 つめ(範囲と正面を変える)の予想の手がかりです。範囲を広げると、「いちばん近い」を探す点が増えます。そのとき、いちばん近い距離は大きくなることがあるか、ないかを考えます。正面を 180° にしたときは、scan_monitor の 4 方向(前・左・後・右)のどれと同じくらいの値になるはずかを予想します。
サンプルと比べる
自分のノードができたら、同じ記録を再生しながら、サンプルの scan_monitor と比べます。scan_monitor は前・左・後・右の 4 方向それぞれで、±30° の範囲のいちばん近い距離を表示します。
ros2 run sakigake_samples scan_monitor
自分のノードの「前」の値が、scan_monitor の「前」と同じくらいになっているかを確かめます。scan_monitor は新しい端末で起動し、端末 3 の自分のノードと同時に動かして、同じ場面の値を並べて撮ります。
結果を見る(予想を書き、自分で実行してから開く)
$ ros2 run sakigake_samples scan_monitor
[INFO] [1789480968.161041463] [scan_monitor]: 前 0.42 m | 左 0.18 m | 後 0.50 m | 右 0.69 m | 450 点 | 5 Hz
[INFO] [1789480969.131299325] [scan_monitor]: 前 0.41 m | 左 0.19 m | 後 0.49 m | 右 0.69 m | 450 点 | 5 Hz
[INFO] [1789480970.131942426] [scan_monitor]: 前 0.41 m | 左 0.18 m | 後 0.50 m | 右 0.69 m | 450 点 | 5 Hz
(中略)
[INFO] [1789480973.134441728] [scan_monitor]: 前 0.43 m | 左 0.19 m | 後 0.49 m | 右 0.69 m | 448 点 | 5 Hz
行の右端の 450 点 は 1 回分の点の数(448 のように回ごとに少し変わる)、5 Hz はその 1 秒間に届いた /scan の数です。
#09 の実験 5 で LiDAR の 0° が正面からずれていた場合は、scan_monitor に正面の角度を渡します(例は 5° ずれていたとき)。正面に置いた箱の点が、赤い矢印より左(反時計回り)に出たら +、右に出たら − の値にします。
ros2 run sakigake_samples scan_monitor --ros-args -p front_angle_deg:=5.0
scan_monitor の判定部分(自分のノードと比べてから開く)
def normalize_angle(angle):
"""角度 [rad] を -π〜π の範囲に直す。"""
return math.atan2(math.sin(angle), math.cos(angle))
def sector_min(ranges, angle_min, angle_increment, center, half_width, range_min, range_max):
nearest = math.inf
for index, distance in enumerate(ranges):
angle = angle_min + index * angle_increment
if abs(normalize_angle(angle - center)) > half_width:
continue
if is_valid_range(distance, range_min, range_max):
nearest = min(nearest, distance)
return nearest
normalize_angle で角度の差を −π〜π に直してから、half_width(30° をラジアンにしたもの)と比べています。is_valid_range で、inf・nan と範囲外の値を取り除いています。有効な点が 1 つも無ければ math.inf を返し、表示では --- になります。
本質の問い
授業の最後に、次の 2 つに答えてください。
- 記録を使えば、ロボットや Wi-Fi の具合に左右されずにノードを試せる。では、記録の再生では確かめられないことは何か。1 つ挙げ、理由を書く。
- この課題のノードを、第 5 週で「前に障害物があったら止まる」ノードに育てるとする。記録で先に確かめておくべきことを 2 つ挙げる。
考え方の例(答えを書いてから開く)
1 つめ:たとえば、自分のノードが送った /cmd_vel でロボットが動いたあとの /scan。記録は過去のデータなので、ノードが何を送っても中身は変わらない。「近づいたら止まる → 止まったので距離が変わらない」のような、ノードの出力が次の入力に返ってくる動きは、実機でしか確かめられない。
2 つめ:たとえば、①正面の角度と範囲が正しいか(箱を動かした場面で値が変わるか)、②有効な点が無いときや、/scan が途切れたときにどう表示するか。②は、記録を一時停止すれば「届かなくなった」場面を作れる。
つまずきやすい点
| 症状 | 見るところ |
|---|---|
ros2 bag record が、フォルダがすでにあると言って止まる | 同じ名前の記録がある。-o room_scan2 のように名前を変える |
ros2 bag info でファイルが見つからないと出る | cd ~/bags で記録のあるフォルダに移動したか。ls で room_scan があるか |
| 再生しているのに RViz に点が出ない | Fixed Frame のつづり。記録に /tf_static を入れたか(ros2 bag info の Topic information で確かめる) |
| 再生した値がときどき実機の値に戻る | robot-disconnect を忘れて、実機の /scan と混ざっている。robot-status で確かめる |
自分のノードの「前」の値が scan_monitor と大きく違う | ±30° の判定(ヒントを参照)と、角度の単位(度とラジアン) |
まとめ
| 確かめたこと | わかったこと |
|---|---|
| 記録(実験 1) | ros2 bag record で、選んだトピックだけをフォルダに保存できる。座標系の /tf・/tf_static も一緒に記録する |
| 記録の中身(実験 2) | ros2 bag info でメッセージの数と時間がわかり、記録していたときの周波数を計算できる |
| 再生(実験 3) | ロボットにつながずに、同じ場面を何度でも再生できる。再生の前には切り離す |
| 課題 | 「前方」は座標系と角度の決め方しだい。記録で先に確かめてから実機で試す |
次の週は、今日作ったノードを出発点に、センサーの値から行動を決めるノード(scan_avoid)の構造を読み、記録と実機の両方で確かめます。




