ここから延長コース A です。コアの 6 週では、std_msgs/msg/String・geometry_msgs/msg/Twist・sensor_msgs/msg/LaserScan のように、すでにある型を使う側にいました。この週は、型を決める側に回ります。
型を決めるというのは、「何を、どんな単位で、どの範囲で受け渡すか」を先に決めることです。決めたものは、送る側と受ける側の両方が同じものを使っていないと、意味が食い違います。この週は、わざとずらしてみて、ROS 2 が止めてくれるのか、止めてくれないのかを自分の目で確かめます。
この週の問い:送る側と受ける側で、型の名前は同じなのに中身(項目の名前や並び)が違っていたら、メッセージは届くでしょうか。届くなら、受ける側にはどんな値が出るでしょうか。答えは A1-4・A1-5 で自分の端末に出た値で確かめ、図は回 A1 の最後の「考え方のすじ道」に載せています。
この記事の位置
| 項目 | 内容 |
|---|---|
| コース | 延長コース A「ROS 2 の部品を自分で作る」(2 週・回 A1〜A4) |
| この記事 | 第 1 週(回 A1・A2)。第 2 週は #A2 です |
| 前に済ませておくこと | コアの第 1〜6 週(#03〜#14)。とくに #04 の設計シートと、#05 のサービス |
| ロボット | 使いません。この 2 週は全部 PC の中で終わります |
| この週で身につけること | 自分で決めた型で、ノードどうしをつなげること。型がずれたときに ROS 2 が何をして、何をしてくれないかを、自分の値で説明できること |
この記事は延長コース Aの 2 回分(A1・A2)です。授業で受ける人は、記録を実習レポート(Word)の「回 A1」「回 A2」のシートに分けて書きます。スクリーンショットは、シートの中の「画面:」の欄(実験の表のすぐ下)に貼ります。延長コースのレポートは、コア(第 1〜12 回)とは別の 1 本にします。ファイルは Google Classroom で配られ、コースの最後に Google Classroom で 1 回だけ出します。期限は最後の実習から 1 週間後の 23:59 です。ひとりで学ぶ人も、予想と結果を同じ形でノートに残しておくと、あとで見返せます。
回 A1 既存の型を読み、項目を 1 つ変えてずらす
1 コマ目は観察と「ずらす実験」です。6 つやります。
A1-1 既存の型を読む
ここでは、すでにある型の中身を端末に出して、項目・型・単位を読みます。
ros2 interface show sensor_msgs/msg/LaserScan
ros2 interface show geometry_msgs/msg/Twist
ros2 interface show は、型の定義(.msg ファイルの中身)をそのまま表示するコマンドです。出てきた項目・型・単位を、ノートに表で写します。写しながら、次の 2 つに答えてください。
LaserScanのrangesは何という型の並びか。何個入るかは、どの項目で決まるかangle_minの単位は度か、ラジアンか。どこを見ればそれが分かるか
メッセージの定義には、単位がコメントで書いてあります。コメントは読む人のためのもので、プログラムは単位を確かめてくれません。これは、このあと自分で型を作るときにそのまま効いてきます。
A1-2 自作メッセージのパッケージを作る
ここでは、型だけを入れるパッケージ my_msgs を作り、ビルドして ROS 2 から見えるようにします。
型を入れるパッケージは、ノードのパッケージとは別に作ります。型の生成には ament_cmake を使うためです(Python のノードは ament_python のままでかまいません)。次のコマンドで、パッケージのひな形と、.msg・.srv を置くフォルダを作ります。
cd ~/ros2_ws/src
ros2 pkg create --build-type ament_cmake my_msgs
cd my_msgs
mkdir msg srv
msg フォルダに FrontDistance.msg、srv フォルダに GetStatus.srv を、次の中身で作ります。1 行が 1 つの項目で、左が型、右が項目の名前です。# から右はコメントです。
# my_msgs/msg/FrontDistance.msg
float32 distance # 前方でいちばん近い物体までの距離[m]
float32 angle # その点の向き[rad]正面を 0、左を正とする
builtin_interfaces/Time stamp # いつ測ったか
# my_msgs/srv/GetStatus.srv
# --- 要求(空でよい)---
---
# --- 応答 ---
bool connected # ロボットにつながっているか
float32 battery # 電池の残り[V]
string mode # いまの動作のモード
.srv は、--- の上が要求、下が応答です。要求が空でもかまいません。
次に、ビルドのときに型を生成させるための行を、CMakeLists.txt と package.xml に足します。rosidl_generate_interfaces には、作った .msg・.srv を全部並べます。
# CMakeLists.txt(project(my_msgs) の下に足す)
find_package(rosidl_default_generators REQUIRED)
find_package(builtin_interfaces REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
"msg/FrontDistance.msg"
"srv/GetStatus.srv"
DEPENDENCIES builtin_interfaces
)
<!-- package.xml に足す 3 行 -->
<buildtool_depend>rosidl_default_generators</buildtool_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>
予想してから実行してください。ビルドする前に ros2 interface show を打つと、新しい型は見えるでしょうか。また、あとで .msg を書き換えてビルドし直したとき、すでに動いているノードは新しい型になるでしょうか(2 つ目の問いは、予想だけ書いておき、A1-4 で確かめます)。
手がかり:ros2 interface show は、~/ros2_ws/install の中にある型を探して表示します。install の中身を作るのは colcon build です。ノードのほうは、起動したときに型を読み込みます。
# ビルドする前に、作った型が見えるかを試す
ros2 interface show my_msgs/msg/FrontDistance
試したら、ビルドします。--packages-select my_msgs は「my_msgs だけをビルドする」という意味です。source install/setup.bash は、ビルドしてできたものを、いま開いている端末に読み込ませるコマンドです。
cd ~/ros2_ws
colcon build --packages-select my_msgs
source install/setup.bash
# 作った型を、ROS 2 の側から見る
ros2 interface show my_msgs/msg/FrontDistance
ros2 interface show my_msgs/srv/GetStatus
| 記録すること | どこで見るか |
|---|---|
ビルドする前の ros2 interface show の表示 | 端末(出た行をそのまま書く) |
| ビルドにかかった時間 | colcon build の最後の行(Summary) |
ros2 interface show my_msgs/msg/FrontDistance の出力 | 端末 |
結果の画面(予想を書いてから開く)
ビルドする前は、型はまだ install の中に無いので見えません。ビルドして source すると見えるようになります(講義用の環境では、端末を開くと ~/ros2_ws/install が自動で読み込まれるので、新しく開いた端末ならそのまま見えます)。そのあとは、ros2 interface show は打った時点の install の中身を見せるので、ビルドし直せば新しい型が出ます。一方、動いているノードは起動したときの型を持ったまま動き続けます。この違いが A1-4 で効いてきます。
A1-3 その型で送って、受ける
ここでは、作った型で値を送るノードと受けるノードを動かし、型の名前が ROS 2 にどう見えているかを確かめます。
送るノードと受けるノードは AI に作らせます。あとの A1-4・A1-5 の手順はこの名前を使うので、次の条件をそのまま渡してください。
| 条件 | 決めた値 | なぜ決めておくか |
|---|---|---|
| 型 | 上の FrontDistance.msg(my_msgs/msg/FrontDistance) | 自分で作った型を使うため。ファイルの中身も貼る |
| 送るノードの名前 | front_distance_pub(実行するときの名前も同じ) | A1-4 で ros2 run するときに使う |
| 受けるノードの名前 | front_distance_sub(実行するときの名前も同じ) | 同じく A1-4 で使う |
| トピック名 | /front_distance | 外から ros2 topic echo などで見るときに使う |
| 送る間隔 | 1 秒に 5 回 | ros2 topic hz で確かめられる値にしておく |
| 送る値 | distance は 0.3〜0.9 m の間をゆっくり変える。angle は 0.0 | 値が動いていれば、届いていることが目で分かる |
| 受ける側の表示 | 1 件ごとに 距離 0.123 m/向き 0.000 rad の形(小数 3 けた)で 1 行出す | A1-4・A1-5 で、受ける側が何を受け取ったかをこの行で読む |
できたら、2 つの端末でそれぞれ動かします。
# 端末 1
ros2 run <自分のパッケージ> front_distance_sub
# 端末 2
ros2 run <自分のパッケージ> front_distance_pub
<自分のパッケージ> は、ノードを入れたパッケージ(ament_python のほう)の名前に置き換えます。動いたら、3 つ目の端末から確かめます。ros2 topic echo は届いている中身を、ros2 topic info -v は型の名前と、送る側・受ける側の数を出します。
ros2 topic list
ros2 topic echo /front_distance
ros2 topic info -v /front_distance
ros2 topic info -v に出る型の名前が my_msgs/msg/FrontDistance になっていることを見ます。ここまでは、コアの #04 でやったことと同じ確かめ方です。
A1-4 ずらす実験:項目の名前を 1 つ変える
ここでは、送る側と受ける側で項目の名前が食い違ったときに何が起きるかを確かめます。
.msg の項目名を 1 つだけ変えます(たとえば distance → range)。送るノードのコードの中の msg.distance も msg.range に直します。そして、送る側だけをビルドし直し、受ける側は古いまま動かします。
# 手順:受ける側を先に動かしておくのがこつ
# 端末 1:受ける側を動かしたままにする(古い型を持ったまま動き続ける)
ros2 run <自分のパッケージ> front_distance_sub
# 端末 2:.msg と送るノードを直してからビルドし直す
cd ~/ros2_ws && colcon build --packages-select my_msgs <自分のパッケージ>
source install/setup.bash
ros2 interface show my_msgs/msg/FrontDistance # 新しい名前になっているか
# 端末 3:送る側だけを、新しい型で起動する
ros2 run <自分のパッケージ> front_distance_pub
予想を先に書いてから実行します。エラーになるか、ならないか。ならないなら、受ける側は何という名前で、どんな値を表示するか。
手がかり:A1-3 の ros2 topic info -v で見た Topic type の欄を思い出してください。項目の名前を変えたとき、この型の名前は変わったでしょうか。また、受ける側のノードは、いつ型を読み込んだのでしょうか(A1-2 の 2 つ目の問い)。
| 記録すること | どこで見るか |
|---|---|
| ビルドし直す前から動いていた受ける側の表示 | 端末 1(出た行を、省略せずそのまま書く) |
ビルドし直したあとの ros2 interface show の出力 | 端末 2 |
| エラーや警告が出たか | 端末 1 と端末 3 |
A1-5 ずらす実験:項目の順番だけ入れ替える
ここでは、項目の名前も型も同じままで、並びだけが食い違ったときに何が起きるかを確かめます。
A1-4 で変えた .msg と送るノードを元に戻してから始めます。こんどは名前も型も同じままで、.msg の行の順番だけを入れ替えます(distance と angle を逆にする)。A1-4 と同じように、受ける側を先に動かしておき、送る側だけビルドし直して起動します。
予想:値が入れ替わって届くでしょうか。それともエラーになるでしょうか。受ける側の端末に出た値を書いてください。
手がかり:送る側は distance に 0.3〜0.9 の値、angle に 0.0 を入れています。受ける側は、届いたデータの中から項目を名前で探して読むのか、前から順に読むのか、を考えます。
A1-6 サービスを作って、契約を確かめる
ここでは、GetStatus.srv を使うサービスを動かし、呼んだときと、相手がいないときの違いを確かめます。
サーバのノードを AI に作らせます。渡す条件は、上の GetStatus.srv、ノード名 status_server、サービス名 /get_status、「応答はいまは決まった値でよい」の 4 つです。呼ぶ側は、ros2 service call で代わりにします。サーバを動かしたら、別の端末で次のコマンドを順に打ちます。ros2 service list -t はサービスの名前と型の一覧、ros2 service type は 1 つのサービスの型、ros2 service call は実際に呼んで応答を出すコマンドです。
ros2 service list -t
ros2 service type /get_status
ros2 interface show my_msgs/srv/GetStatus
ros2 service call /get_status my_msgs/srv/GetStatus "{}"
"{}" は、要求が空であることを表します。
最後に、サーバのノードを Ctrl+C で止めてから、同じ呼び出しをします。予想:待ち続けるのか、エラーになるのか。
手がかり:#05 で、受ける側のいないトピックに送ったとき、送る側の端末には何か出たでしょうか。サービスは「呼んだら答えが返る」しくみなので、呼んだ側は答えを待ちます。
結果の画面(予想を書いてから開く)
待ち続けている呼び出しは、Ctrl+C で止めます。
この回で撮っておく画面
動画は撮りません。そのかわり、下の画面を撮って残します。授業で受ける人は、実習レポートの「回 A1」の、それぞれの「画面:」の欄に貼ります。ひとりで学ぶ人も、同じ画面を残しておくと、あとで値を見直せます。最初の 1 枚は、実習を始める前に端末で TZ=Asia/Tokyo date と打ち、日付と時刻が出た画面です。授業の日に実習したことの記録になります。講義用の環境の時計は世界標準時(UTC)なので、TZ=Asia/Tokyo を付けて日本時間で出します。
TZ=Asia/Tokyo dateの画面(回の頭)ros2 interface showの出力(実験 A1-1 の欄)colcon buildが終わった端末(実験 A1-2 の欄)- 名前を変えたあと、受ける側に出た行(実験 A1-4 の欄)
- 順番を入れ替えたあと、受ける側に出た行(実験 A1-5 の欄)
ros2 service callの応答(実験 A1-6 の欄)
本質の問い(回 A1)
授業の最後に、次の問いに答えてください。
- 中身の違う
.msgを送る側と受ける側で使ったときに起きることを、自分の値で説明しなさい。(A1-4・A1-5 で書き写した表示を根拠に使います) - この結果をふまえて、設計シートに単位と範囲を書く意味を説明しなさい。
考え方のすじ道(答えを書いてから開く)
ROS 2 が購読をつなぐときに見ているのは、型の名前(ここでは my_msgs/msg/FrontDistance)です。項目の名前を変えても、並びを入れ替えても、型の名前が同じままなら、通信はつながってしまいます。受け取った側は、届いたバイトの並びを自分が持っている .msg の順に読みます。だから、並びを入れ替えたときは、値が入れ替わったまま、エラーも警告も出ずに届きます。
つまり、「同じ型を使う」という契約を守っているのは ROS 2 ではなく、同じ .msg をビルドし直して使っている人の側だということです。単位を m にするか cm にするか、正面を 0 とするか π/2 とするかも、同じ話です。型は形を合わせてくれますが、意味は合わせてくれません。
授業で書くのは、この一般論ではなく、自分の端末に出た文です。受ける側に出た項目名と値、ros2 topic info -v の購読の数がいくつだったかを、どの実験の記録か(A1-4 など)を添えて示してください。
回 A2 設計シート → AI → 検証
2 コマ目は、コアの #04 と同じ流れです。違うのは、型そのものを自分で決めるところです。
- 設計シートを書く:「ロボットの状態を返すサービス」の契約を決めます。名前・要求・応答の項目・単位・範囲・接続状態の切り替え方・呼んではいけない条件を書きます。シートの欄は自分で埋めます。AI には、書いたシートに抜けがないかを質問させるだけにします。
- AI に実装させる:設計シートを貼って、パッケージと
.srv、サーバノードを作らせます。colcon buildが通るまで直し、どこを直したかを自分の言葉で書きます。 - 検証する:
ros2 service list -t・ros2 interface show・ros2 service callの 3 つで、設計シートのとおりかを表にします。設計シートに書いた「呼んではいけない条件」をros2 param setで実際に作り、そのときの応答を記録します。 - 交換する:隣の人と
.srvファイルだけを交換し、相手のサーバを自分のクライアントで呼びます。通らなければ、設計シートのどこが食い違っていたかを書きます。
設計シートの例
ここでは、設計シートの書き方を、例と欄ごとの説明で確かめます。
サービス名:/get_status
役割:ロボットの状態を 1 回だけ聞く
サーバのノード名:status_server
要求:なし
応答:connected(bool)
battery(float32、単位 V、範囲 0.0〜12.6 ※例の値)
mode(string、"idle" か "walking" か "error" の 3 つだけ)
接続状態の切り替え:パラメータ connected(bool、既定 true)で切り替える
(ロボットを使わない回なので、つながっていない場面を、このパラメータで作る)
呼んではいけない条件・そのときの応答:
- ロボットにつながっていないとき → connected = false、battery = 0.0、mode = "error"
(つながっていないことを、エラーではなく値で返す。呼んだ側が分岐できるようにするため)
- 電池の値が読めないとき → battery = -1.0(0.0 は「空」と誤解されるので使わない)
検証の方法:
1. ros2 interface show my_msgs/srv/GetStatus が、この表のとおりか
2. ros2 service call /get_status my_msgs/srv/GetStatus "{}" で応答が返るか
3. ros2 param set /status_server connected false のあと、同じ呼び出しで上の応答になるか
4. 範囲外(mode に決めていない文字列)を返す作りになっていないか、コードを読んで確かめる
欄ごとに、なぜ書くのかを示します。
| 欄 | 例の書き方 | なぜ書くか |
|---|---|---|
| サービス名 | /get_status | 呼ぶ側と同じ名前でないと見つからない。ros2 service list で確かめる値になる |
| 役割 | 状態を 1 回だけ聞く | トピックで流し続けるのではなく、サービスにした理由を残す |
| サーバのノード名 | status_server | ros2 param set /status_server ... のように、外から操作するときに名前が要る |
| 要求 | なし | 要求が空でも「空と決めた」と書く。書かないと AI が勝手に項目を足すことがある |
| 応答の項目と型 | connected(bool)など | .srv の 1 行 1 行になる。ros2 interface show と見比べる元になる |
| 単位 | battery は V | A1 で見たとおり、プログラムは単位を確かめない。人が読んで合わせるために書く |
| 範囲 | battery は 0.0〜12.6(例の値) | 範囲の外の値が出たら、どこかがおかしいと気づける。数字はこの例のための仮の値で、実際のロボットの電池に合わせて決める |
| 取りうる値 | mode は 3 つだけ | 文字列は何でも入ってしまうので、決めた値以外を返していないかをコードで確かめられるようにする |
| 接続状態の切り替え | パラメータ connected(既定 true) | ロボットを使わずに「つながっていない」場面を作るため。検証の表の 4 行目で使う |
| 呼んではいけない条件と応答 | つながっていないとき → 値で返す | AI は、書いていない場面の振る舞いを勝手に決める。決めておけば、できたコードが設計どおりかを読んで確かめられる |
| 検証の方法 | 使うコマンドを並べる | 「できた」と言える条件を、作る前に決めておくため |
connected は応答の項目の名前でもあり、パラメータの名前でもあります。パラメータは「サーバに、つながっていないふりをさせる」ためのスイッチで、応答の connected は「呼んだ側に、いまつながっているかを知らせる」ための値です。
AI に頼むときは、たとえば次のように書いて、自分の設計シートを貼ります。
次の設計シートのとおりに、サービス /get_status を出す Python のノードを作ってください。
・パッケージは ament_python で、型は my_msgs/srv/GetStatus(もう作ってあります)を使います。
・ノード名は status_server にしてください。
・接続しているかどうかは、パラメータ connected(bool、既定 true)で切り替えられるようにしてください。
ノードを動かしたまま ros2 param set で変えたら、次の呼び出しから応答が変わるようにします。
・設計シートに書いていない場面の振る舞いを決めたときは、コードの中にコメントで書いてください。
(ここに自分の設計シートを貼る)
検証の表(そのままレポートに使えます)
ここでは、できたサーバが設計シートのとおりかを、外から 1 行ずつ確かめます。4 行目の「呼んではいけない条件」は、次のようにパラメータを変えて作ります。
# 端末 1:サーバを動かす
ros2 run <自分のパッケージ> status_server
# 端末 2:正しい呼び方で呼ぶ
ros2 service call /get_status my_msgs/srv/GetStatus "{}"
# 端末 2:動かしたまま「つながっていない」場面を作り、同じ呼び出しをする
ros2 param set /status_server connected false
ros2 service call /get_status my_msgs/srv/GetStatus "{}"
| 確かめること | 使うコマンド | 設計の値 | 実際の値 | 一致 |
|---|---|---|---|---|
| サービスの名前 | ros2 service list -t | /get_status | ||
| 応答の項目と型 | ros2 interface show | bool / float32 / string | ||
| 正しい呼び方での応答 | ros2 service call | connected = true | ||
| 呼んではいけない条件のとき | ros2 param set /status_server connected false で条件を作ってから ros2 service call | connected = false / battery = 0.0 / mode = error | ||
隣の人の .srv で呼ぶ | ros2 service call | 同じ応答が返る |

この回で撮っておく画面
動画は撮りません。そのかわり、下の画面を撮って残します。授業で受ける人は、実習レポートの「回 A2」の、それぞれの「画面:」の欄に貼ります。ひとりで学ぶ人も、同じ画面を残しておくと、あとで値を見直せます。最初の 1 枚は、実習を始める前に端末で TZ=Asia/Tokyo date と打ち、日付と時刻が出た画面です。授業の日に実習したことの記録になります。講義用の環境の時計は世界標準時(UTC)なので、TZ=Asia/Tokyo を付けて日本時間で出します。
TZ=Asia/Tokyo dateの画面(回の頭)- 設計シート(写真)(実験 A2-1 の欄)
colcon buildの最後(実験 A2-2 の欄)ros2 interface showと設計シートを見比べた画面(実験 A2-3 の欄)- 呼んではいけない条件を作ったときの応答(実験 A2-4 の欄)
- 隣の人のサーバを呼んだ結果(実験 A2-5 の欄)
つまずきやすい点
| 症状 | 見るところ |
|---|---|
colcon build が「rosidl」で止まる | CMakeLists.txt の rosidl_generate_interfaces に、作った .msg・.srv を並べ忘れていないか。package.xml の 3 行が入っているか |
ros2 interface show で「見つからない」と言われる | その端末で source install/setup.bash をしたか。ビルドし直したあとは、開いている全部の端末でやり直す |
Python のノードから import できない | 型のパッケージ(ament_cmake)と、ノードのパッケージ(ament_python)を分けているか。ノード側の package.xml に <depend>my_msgs</depend> があるか |
| 型は合っているのにつながらない | トピックの名前(ros2 topic list)と名前空間。#06 でやった remap の見落としがないか |
ros2 service call が返ってこない | サーバのノードが動いているか(ros2 node list)。サービス名が合っているか(ros2 service list) |
ros2 param set がうまくいかない(エラーが出る、応答が変わらない) | ノード名(ros2 node list)とパラメータ名(ros2 param list /status_server)が設計シートと合っているか |
AI が .msg に単位を書かない | コメントで単位を書くのは人の仕事。書いていないと、次の週の自分と、隣の人が困る |
まとめ
| やったこと | わかったこと |
|---|---|
| 既存の型を読む | 型には項目・型・単位が書いてある。単位はコメントで、プログラムは確かめてくれない |
.msg を作る | 型のパッケージは ament_cmake で別に作り、ビルドして source し直すと使える |
| 名前を変えてずらす | ROS 2 が見ているのは型の名前だけ。項目名が違ってもエラーは出ずに届く |
| 順番を入れ替える | 受ける側は自分の .msg の順に読む。値が入れ替わったまま、警告も出ずに届く |
.srv を作る | 要求と応答を --- で分ける。サーバがいないときの振る舞いも契約に含まれる |
| 設計シートで作らせる | 範囲と「呼んではいけない条件」を書いておくと、できたコードを読んで確かめられる |
次の #A2 では、1 つのノードの中で処理が詰まるとどう見えるかを観察し、executor とコールバックグループで直します。あわせて、launch の引数とテスト(colcon test)を扱います。







