Skip to content

カスタムインターフェースの実装

目標: 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ファイル用のディレクトリを作成します:

bash
ros2 pkg create --build-type ament_cmake --license Apache-2.0 more_interfaces
mkdir more_interfaces/msg

2 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を開いて以下の行を追加します:

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ファイルからメッセージコードを生成するパッケージを見つけます:

cmake
find_package(rosidl_default_generators REQUIRED)

生成したいメッセージのリストを宣言します:

cmake
set(msg_files
  "msg/AddressBook.msg"
)

.msgファイルを手動で追加することで、他の.msgファイルを追加した後にCMakeがプロジェクトを再構成する必要があることを確実にします。

メッセージを生成します:

cmake
rosidl_generate_interfaces(${PROJECT_NAME}
  ${msg_files}
)

また、メッセージランタイム依存関係をエクスポートすることも確認してください:

cmake
ament_export_dependencies(rosidl_default_runtime)

これでmsg定義からソースファイルを生成する準備ができました。 下のステップ4ですべてまとめて行うため、今はコンパイルステップをスキップします。

3 同じパッケージからのインターフェースの使用

これでこのメッセージを使用するコードを書き始めることができます。

more_interfaces/srcpublish_address_book.cppというファイルを作成し、以下のコードを貼り付けます:

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のヘッダーをインクルードします。

cpp
#include "more_interfaces/msg/address_book.hpp"

ノードとAddressBookパブリッシャーを作成します。

cpp
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");

メッセージを定期的にパブリッシュするためのコールバックを作成します。

cpp
auto publish_msg = [this]() -> void {

後でパブリッシュするAddressBookメッセージインスタンスを作成します。

cpp
auto message = more_interfaces::msg::AddressBook();

AddressBookフィールドを設定します。

cpp
message.first_name = "John";
message.last_name = "Doe";
message.phone_number = "1234567890";
message.phone_type = message.PHONE_TYPE_MOBILE;

最後にメッセージを定期的に送信します。

cpp
std::cout << "Publishing Contact\nFirst:" << message.first_name <<
  "  Last:" << message.last_name << std::endl;

this->address_book_publisher_->publish(message);

1秒ごとにpublish_msg関数を呼び出すための1秒タイマーを作成します。

cpp
timer_ = this->create_wall_timer(1s, publish_msg);

3.2 パブリッシャーのビルド

CMakeLists.txtでこのノードの新しいターゲットを作成する必要があります:

cmake
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コードを使用する必要があります:

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 試してみる

ワークスペースのルートに戻ってパッケージをビルドします:

bash
cd ~/ros2_ws
colcon build --packages-up-to more_interfaces

その後、ワークスペースをソースしてパブリッシャーを実行します:

bash
source install/local_setup.bash
ros2 run more_interfaces publish_address_book

publish_address_book.cppで設定した値を含む、定義したmsgをパブリッシャーが中継しているのが見えるはずです。

メッセージがaddress_bookトピックでパブリッシュされていることを確認するには、別のターミナルを開き、ワークスペースをソースしてtopic echoを呼び出します:

bash
source install/setup.bash
ros2 topic echo /address_book

このチュートリアルではサブスクライバーを作成しませんが、練習のために自分で作成してみることができます(簡単なパブリッシャーとサブスクライバーの作成 (C++)を参考にしてください)。

5 (追加)既存のインターフェース定義の使用

注意

新しいインターフェース定義で既存のインターフェース定義を使用することができます。 例えば、既存のROS 2パッケージrosidl_tutorials_msgsに属するContact.msgという名前のメッセージがあるとします。 その定義が前述のカスタム作成したAddressBook.msgインターフェースと同一であると仮定します。

その場合、AddressBook.msg(ノードと同じパッケージのインターフェース)をContact型(別のパッケージのインターフェース)として定義できたでしょう。 AddressBook.msgContact型の配列として定義することさえできます:

rosidl_tutorials_msgs/Contact[] address_book

このメッセージを生成するには、package.xmlContact.msgのパッケージrosidl_tutorials_msgsに依存関係を宣言する必要があります:

xml
<build_depend>rosidl_tutorials_msgs</build_depend>

<exec_depend>rosidl_tutorials_msgs</exec_depend>

そしてCMakeLists.txtで:

cmake
find_package(rosidl_tutorials_msgs REQUIRED)

rosidl_generate_interfaces(${PROJECT_NAME}
  ${msg_files}
  DEPENDENCIES rosidl_tutorials_msgs
)

また、address_bookcontactsを追加できるようにするために、パブリッシャーノードでContact.msgのヘッダーをインクルードする必要があります。

cpp
#include "rosidl_tutorials_msgs/msg/contact.hpp"

コールバックを以下のようなものに変更できます:

cpp
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.xmlCMakeLists.txt#include文についても学びました。

次のステップ

次に、ローンチファイルから設定することを学ぶカスタムパラメータを持つ簡単なROS 2パッケージを作成します。 再び、C++またはPythonのいずれかで書くことを選択できます。

関連コンテンツ

ROS 2インターフェースとIDL(インターフェース定義言語)に関するいくつかの設計記事があります。

Released under the MIT License.