環境
本記事は以下の環境を想定した記事である。
項目 | 値 |
---|---|
OS | Ubuntu 22.04 |
ROS | ROS 2 Humble |
概要
このページでは、ROS(ROS 1、ROS 2共通)の基本となる通信手段であるService通信について説明し、それをROS 2で実装する。
このページの内容はROS 2公式チュートリアルをベースとする。(ROS 2公式チュートリアル)
公式チュートリアルのサンプルコードはmain関数で処理の大半を実装しているが、本記事のコードではクラスの中に通信処理を実装する。
本記事で解説していること
- ROSのService通信の概念
- clientのソースコード(C++)
- serverのソースコード(C++)
この記事では、example_interfaces/srv/AddTwoInts
型のサービスによるノード間通信をC++で実装する。このサービスは、2つの数値をリクエストとして受け取り、その和をレスポンスとして返すという簡単なサービスである。
Service通信
Service通信はROSのノード間の通信方法の1つである。Service通信はリクエスト・レスポンス型の通信方法であり、Pub&Sub通信とは通信方法が異なる。
Service通信では、clientがある処理のリクエストを送信する。serverはそれを受け取り、処理を行った結果をレスポンスとして返す。clientはこの結果を受け取ることで、通信が成立する。
この概念図を以下に示す。
トピックと同様に、サービスにもメッセージ型がある。Service通信はメッセージ型が一致している場合にのみ成立する。
サービス定義ファイル
サービスのリクエストとレスポンスには様々なデータを含められる。ROS 2のデータ型を組み合わせることで、独自のサービス型を実装できる。
今回用いるサービス定義ファイルは、以下のようになる。
int64 a
int64 b
---
int64 sum
---
の上段はリクエスト、下段はレスポンスのデータである。
int64
はROS 2のデータ型である。
今回作成するService通信では、整数値aとbの値がリクエストに含まれ、レスポンスにはそれらの和であるsumが入る。
メッセージ型の説明と、独自のメッセージ型を作成して利用する方法については、「実習ROS 2 ROS 2メッセージ」、「実習ROS 2 カスタムROSメッセージ」で解説する。
前準備
ROS 2のプログラムを作成するためには、Pub&Sub通信のときと同様の前準備が必要である。
ここでは、~/ros2_lecture_ws
がROS 2のワークスペースであり、パッケージ名がservice_comm
であると仮定して説明する。パッケージは以下のコマンドで作成したと仮定する。
ros2 pkg create --build-type ament_cmake service_comm --dependencies rclcpp example_interfaces
--dependencies
オプションによって、パッケージを作る時点で依存パッケージを指定できる。
service_comm
パッケージはAddTwoInts
サービスのメッセージ型を使用する。
そのため、この型が定義されているexample_interfaces
パッケージを依存パッケージとして指定する。
ソースファイルの作成
Service通信を行うプログラムを作成するために、以下の4つを行う。
- clientのコーディング
- serverのコーディング
- 依存関係の設定
- CMakeLists.txtの設定
clientのコーディング
以下のリンク先のソースコードを実装する。
clientは引数を2つ受け取り、その値をリクエストとしてサーバに送信し、結果を表示するプログラムである。引数のチェックと変換はmain関数で、リクエストの生成と送信、結果の表示はAddTwoIntsClientクラスで行う。
実際にサービスクライアントを生成しているのは、以下の部分である。
// サービスクライアントの生成
client_ = this->create_client<example_interfaces::srv::AddTwoInts>("add_two_ints");
リクエストを送信する処理は、送信後にレスポンスを待ち続けて処理が止まらないように、非同期(async)で行う。
// リクエストを送信
auto result = client_->async_send_request(request);
serverのコーディング
以下のリンク先のソースコードを実装する。
serverはPub&Sub通信におけるsubscriberのコードと実装が似ている。subscriberの代わりに、サービスサーバを示す変数によって、サーバを実現する。
サービスサーバを生成している場所は以下の場所である。リクエストを受け取ったときに実行されるコールバック関数として、AddTwoIntsServer::add
関数を指定する。
// サービスサーバの生成
server_ = this->create_service<example_interfaces::srv::AddTwoInts>(
"add_two_ints",
std::bind(&AddTwoIntsServer::add, this, std::placeholders::_1, std::placeholders::_2));
サービス本体の処理になる、2つの数値の加算を行うAddTwoIntsServer::add
関数を以下に示す。引数にリクエストとレスポンスを持っており、これらを介してメッセージ通信を行う。
// リクエストを受け取ったときに呼び出されるコールバック関数の定義
void add(
const std::shared_ptr<example_interfaces::srv::AddTwoInts::Request> request,
std::shared_ptr<example_interfaces::srv::AddTwoInts::Response> response)
{
RCLCPP_INFO(this->get_logger(), "Received request: a = %ld, b = %ld", request->a, request->b);
// サービスの処理本体
response->sum = request->a + request->b;
RCLCPP_INFO(this->get_logger(), "Sending back response: sum = %ld", response->sum);
}
依存関係の設定
Pub&Sub通信のときと同様に、package.xml
に必要なメタデータを記述する。
以下のリンク先のように記述する。
CMakeLists.txtの設定
Pub&Sub通信のときと同様に、CMakeLists.txt
に必要なビルド情報を記述する。
以下のリンク先のように記述する。
ビルド
Pub&Sub通信のときと同様に、以下のコマンドでビルドする。
cd ~/ros2_lecture_ws
colcon build --packages-select service_comm
実行
1つ目のターミナルで、以下を実行する。
cd ~/ros2_lecture_ws # ワークスペースへの移動
. install/setup.bash # ワークスペース環境のセットアップ
ros2 run service_comm add_two_ints_server # 実行
2つ目のターミナルで、以下を実行する。
引数の10と20は足し算を行う2つの数字である。
cd ~/ros2_lecture_ws
. install/setup.bash
ros2 run service_comm add_two_ints_client 10 20
結果
clientを起動してからserverを起動すると、以下のような実行結果が得られる。clientで与えた10と20という値の和である30を正しく表示できている。
# clientのターミナル
$ ros2 run service_comm add_two_ints_client 10 20
[INFO] [1659661416.499417251] [add_two_ints_client]: Service is not available. waiting...
[INFO] [1659661417.499673994] [add_two_ints_client]: Service is not available. waiting...
[INFO] [1659661418.499830419] [add_two_ints_client]: Service is not available. waiting...
[INFO] [1659661419.500242970] [add_two_ints_client]: Service is not available. waiting...
[INFO] [1659661420.500420660] [add_two_ints_client]: Service is not available. waiting...
[INFO] [1659661421.500860678] [add_two_ints_client]: Service is not available. waiting...
[INFO] [1659661422.501306595] [add_two_ints_client]: Service is not available. waiting...
[INFO] [1659661422.695416949] [add_two_ints_client]: Send Request.
[INFO] [1659661422.695600144] [add_two_ints_client]: Sum: 30
# serverのターミナル
$ ros2 run service_comm add_two_ints_server
[INFO] [1659661422.695377444] [add_two_ints_server]: Create AddTwoInts service server.
[INFO] [1659661422.695472253] [add_two_ints_server]: Received request: a = 10, b = 20
[INFO] [1659661422.695482863] [add_two_ints_server]: Sending back response: sum = 30
また、serverを起動せずにclientのみを起動し、途中でctrl+Cにより実行を中断すると、以下のような結果になる。
$ ros2 run service_comm add_two_ints_client 10 20
[INFO] [1659661641.420232901] [add_two_ints_client]: Service is not available. waiting...
[INFO] [1659661642.420483827] [add_two_ints_client]: Service is not available. waiting...
[INFO] [1659661643.420632357] [add_two_ints_client]: Service is not available. waiting...
[INFO] [1659661644.420781304] [add_two_ints_client]: Service is not available. waiting...
[INFO] [1659661645.421282922] [add_two_ints_client]: Service is not available. waiting...
^C[INFO] [1659661645.949606772] [rclcpp]: signal_handler(signal_value=2)
なお、clientを引数なしで実行すると、以下のように表示されて実行が終了する。
$ ros2 run service_comm add_two_ints_client
[INFO] [1659661670.320935396] [rclcpp]: USAGE: add_two_ints client <int> <int>
補足
- ROS 1は同期型のService通信のみが可能であったが、ROS 2では同期型に加えて非同期型のService通信も可能になった。
- 本記事では
int64_t
型を出力するための書式指定子は%ld
としたが、正式にはPRId64
を用いて以下のように記述する。
#include <cinttypes>
RCLCPP_INFO(this->get_logger(), "Sum: %" PRId64, result.get()->sum);