カスタムインターフェースの実装
目標: ROS 2でカスタムインターフェースを実装するより多くの方法を学ぶ。
チュートリアルレベル: 初心者
所要時間: 15分
背景
前のチュートリアルでは、カスタムmsgとsrvインターフェースの作成方法を学びました。
ベストプラクティスは専用のインターフェースパッケージでインターフェースを宣言することですが、時には1つのパッケージでインターフェースの宣言、作成、使用をすべて行うと便利な場合があります。
インターフェースは現在CMakeパッケージでのみ定義できることを思い出してください。 ただし、CMakeパッケージでPythonライブラリとノードを持つことは可能です(ament_cmake_pythonを使用)。したがって、1つのパッケージでインターフェースとPythonノードを一緒に定義することができます。 簡単にするため、ここではCMakeパッケージとC++ノードを使用します。
このチュートリアルはmsgインターフェースタイプに焦点を当てますが、ここでの手順はすべてのインターフェースタイプに適用できます。
前提条件
このチュートリアルを進める前に、カスタムmsgとsrvファイルの作成チュートリアルの基本を確認したと仮定します。
ROS 2がインストールされ、ワークスペースがあり、パッケージの作成について理解している必要があります。
いつものように、開く新しいターミナルごとにROS 2をソースすることを忘れないでください。
タスク
1 パッケージの作成
ワークスペースのsrcディレクトリで、パッケージmore_interfacesを作成し、その中にmsgファイル用のディレクトリを作成します:
ros2 pkg create --build-type ament_cmake --license Apache-2.0 more_interfaces
mkdir more_interfaces/msg2 msgファイルの作成
more_interfaces/msg内で、新しいファイルAddressBook.msgを作成し、個人に関する情報を運ぶことを意図したメッセージを作成するために以下のコードを貼り付けます:
uint8 PHONE_TYPE_HOME=0
uint8 PHONE_TYPE_WORK=1
uint8 PHONE_TYPE_MOBILE=2
string first_name
string last_name
string phone_number
uint8 phone_typeこのメッセージは以下のフィールドで構成されています:
- first_name: string型
- last_name: string型
- phone_number: string型
- phone_type: uint8型、いくつかの名前付き定数値が定義されている
メッセージ定義内でフィールドのデフォルト値を設定することも可能です。 インターフェースをカスタマイズするその他の方法については、インターフェースを参照してください。
次に、msgファイルがC++、Python、その他の言語用のソースコードに変換されることを確認する必要があります。
2.1 msgファイルのビルド
package.xmlを開いて以下の行を追加します:
<buildtool_depend>rosidl_default_generators</buildtool_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>ビルド時にはrosidl_default_generatorsが必要で、実行時にはrosidl_default_runtimeのみが必要であることに注意してください。
CMakeLists.txtを開いて以下の行を追加します:
msg/srvファイルからメッセージコードを生成するパッケージを見つけます:
find_package(rosidl_default_generators REQUIRED)生成したいメッセージのリストを宣言します:
set(msg_files
"msg/AddressBook.msg"
).msgファイルを手動で追加することで、他の.msgファイルを追加した後にCMakeがプロジェクトを再構成する必要があることを確実にします。
メッセージを生成します:
rosidl_generate_interfaces(${PROJECT_NAME}
${msg_files}
)また、メッセージランタイム依存関係をエクスポートすることも確認してください:
ament_export_dependencies(rosidl_default_runtime)これでmsg定義からソースファイルを生成する準備ができました。 下のステップ4ですべてまとめて行うため、今はコンパイルステップをスキップします。
3 同じパッケージからのインターフェースの使用
これでこのメッセージを使用するコードを書き始めることができます。
more_interfaces/srcでpublish_address_book.cppというファイルを作成し、以下のコードを貼り付けます:
#include <chrono>
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "more_interfaces/msg/address_book.hpp"
using namespace std::chrono_literals;
class AddressBookPublisher : public rclcpp::Node
{
public:
AddressBookPublisher()
: Node("address_book_publisher")
{
address_book_publisher_ =
this->create_publisher<more_interfaces::msg::AddressBook>("address_book", 10);
auto publish_msg = [this]() -> void {
auto message = more_interfaces::msg::AddressBook();
message.first_name = "John";
message.last_name = "Doe";
message.phone_number = "1234567890";
message.phone_type = message.PHONE_TYPE_MOBILE;
std::cout << "Publishing Contact\nFirst:" << message.first_name <<
" Last:" << message.last_name << std::endl;
this->address_book_publisher_->publish(message);
};
timer_ = this->create_wall_timer(1s, publish_msg);
}
private:
rclcpp::Publisher<more_interfaces::msg::AddressBook>::SharedPtr address_book_publisher_;
rclcpp::TimerBase::SharedPtr timer_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<AddressBookPublisher>());
rclcpp::shutdown();
return 0;
}3.1 コードの説明
新しく作成したAddressBook.msgのヘッダーをインクルードします。
#include "more_interfaces/msg/address_book.hpp"ノードとAddressBookパブリッシャーを作成します。
using namespace std::chrono_literals;
class AddressBookPublisher : public rclcpp::Node
{
public:
AddressBookPublisher()
: Node("address_book_publisher")
{
address_book_publisher_ =
this->create_publisher<more_interfaces::msg::AddressBook>("address_book");メッセージを定期的にパブリッシュするためのコールバックを作成します。
auto publish_msg = [this]() -> void {後でパブリッシュするAddressBookメッセージインスタンスを作成します。
auto message = more_interfaces::msg::AddressBook();AddressBookフィールドを設定します。
message.first_name = "John";
message.last_name = "Doe";
message.phone_number = "1234567890";
message.phone_type = message.PHONE_TYPE_MOBILE;最後にメッセージを定期的に送信します。
std::cout << "Publishing Contact\nFirst:" << message.first_name <<
" Last:" << message.last_name << std::endl;
this->address_book_publisher_->publish(message);1秒ごとにpublish_msg関数を呼び出すための1秒タイマーを作成します。
timer_ = this->create_wall_timer(1s, publish_msg);3.2 パブリッシャーのビルド
CMakeLists.txtでこのノードの新しいターゲットを作成する必要があります:
find_package(rclcpp REQUIRED)
add_executable(publish_address_book src/publish_address_book.cpp)
ament_target_dependencies(publish_address_book rclcpp)
install(TARGETS
publish_address_book
DESTINATION lib/${PROJECT_NAME})3.3 インターフェースに対するリンク
同じパッケージで生成されたメッセージを使用するために、以下のCMakeコードを使用する必要があります:
rosidl_get_typesupport_target(cpp_typesupport_target
${PROJECT_NAME} rosidl_typesupport_cpp)
target_link_libraries(publish_address_book "${cpp_typesupport_target}")これはAddressBook.msgから関連する生成されたC++コードを見つけ、ターゲットがそれに対してリンクできるようにします。
使用されているインターフェースが独立してビルドされた異なるパッケージからのものである場合、このステップは必要なかったことに気づいたかもしれません。 このCMakeコードは、定義されているのと同じパッケージでインターフェースを使用したい場合にのみ必要です。
4 試してみる
ワークスペースのルートに戻ってパッケージをビルドします:
cd ~/ros2_ws
colcon build --packages-up-to more_interfacesその後、ワークスペースをソースしてパブリッシャーを実行します:
source install/local_setup.bash
ros2 run more_interfaces publish_address_bookpublish_address_book.cppで設定した値を含む、定義したmsgをパブリッシャーが中継しているのが見えるはずです。
メッセージがaddress_bookトピックでパブリッシュされていることを確認するには、別のターミナルを開き、ワークスペースをソースしてtopic echoを呼び出します:
source install/setup.bash
ros2 topic echo /address_bookこのチュートリアルではサブスクライバーを作成しませんが、練習のために自分で作成してみることができます(簡単なパブリッシャーとサブスクライバーの作成 (C++)を参考にしてください)。
5 (追加)既存のインターフェース定義の使用
注意
新しいインターフェース定義で既存のインターフェース定義を使用することができます。 例えば、既存のROS 2パッケージrosidl_tutorials_msgsに属するContact.msgという名前のメッセージがあるとします。 その定義が前述のカスタム作成したAddressBook.msgインターフェースと同一であると仮定します。
その場合、AddressBook.msg(ノードと同じパッケージのインターフェース)をContact型(別のパッケージのインターフェース)として定義できたでしょう。 AddressBook.msgをContact型の配列として定義することさえできます:
rosidl_tutorials_msgs/Contact[] address_bookこのメッセージを生成するには、package.xmlでContact.msgのパッケージrosidl_tutorials_msgsに依存関係を宣言する必要があります:
<build_depend>rosidl_tutorials_msgs</build_depend>
<exec_depend>rosidl_tutorials_msgs</exec_depend>そしてCMakeLists.txtで:
find_package(rosidl_tutorials_msgs REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
${msg_files}
DEPENDENCIES rosidl_tutorials_msgs
)また、address_bookにcontactsを追加できるようにするために、パブリッシャーノードでContact.msgのヘッダーをインクルードする必要があります。
#include "rosidl_tutorials_msgs/msg/contact.hpp"コールバックを以下のようなものに変更できます:
auto publish_msg = [this]() -> void {
auto msg = std::make_shared<more_interfaces::msg::AddressBook>();
{
rosidl_tutorials_msgs::msg::Contact contact;
contact.first_name = "John";
contact.last_name = "Doe";
contact.phone_number = "1234567890";
contact.phone_type = message.PHONE_TYPE_MOBILE;
msg->address_book.push_back(contact);
}
{
rosidl_tutorials_msgs::msg::Contact contact;
contact.first_name = "Jane";
contact.last_name = "Doe";
contact.phone_number = "4254242424";
contact.phone_type = message.PHONE_TYPE_HOME;
msg->address_book.push_back(contact);
}
std::cout << "Publishing address book:" << std::endl;
for (auto contact : msg->address_book) {
std::cout << "First:" << contact.first_name << " Last:" << contact.last_name <<
std::endl;
}
address_book_publisher_->publish(*msg);
};これらの変更をビルドして実行すると、期待通りに定義されたmsgと、上記で定義されたmsgの配列が表示されます。
まとめ
このチュートリアルでは、インターフェースを定義するためのさまざまなフィールドタイプを試し、それが使用されているのと同じパッケージでインターフェースをビルドしました。
また、別のインターフェースをフィールドタイプとして使用する方法、およびその機能を利用するために必要なpackage.xml、CMakeLists.txt、#include文についても学びました。
次のステップ
次に、ローンチファイルから設定することを学ぶカスタムパラメータを持つ簡単なROS 2パッケージを作成します。 再び、C++またはPythonのいずれかで書くことを選択できます。
関連コンテンツ
ROS 2インターフェースとIDL(インターフェース定義言語)に関するいくつかの設計記事があります。