MoriKen's Journal

MoriKen's Journal

アラサー社会人博士による徒然日記。技術についてつらつら。だけだとコンテンツが貧弱なので、会社公認で大学院博士課程に進学した経緯や、独学でTOEICを475→910にしたノウハウを共有します。

【ROS 2】msg および srv インタフェースの紹介(公式文書和訳)

Sponsored Link

ROS 2 公式文書(英語) 日本語訳シリーズです。

本ブログの日本語翻訳版のトップページは以下のリンクを参照下さい。

www.moriken254.com

※2019/05/08 現在のものです。

※作成中:これは、カスタム ROS I/F を作成および使用するためのチュートリアルのドラフトです。

※免責事項:提供されているコードは説明上の補助的なものであり、古くなっている可能性があり、そのままコンパイルできない場合もあります。ご了承下さい。

  • msg : msg ファイルは ROS メッセージのフィールドを記述する単純なテキストファイルです。種々の開発言語のメッセージのソースコードを生成するのに使われます。

  • srv : srv ファイルはサービスを記述します。リクエストとレスポンスの記述部分によって構成されています。リクエストとレスポンスはメッセージ宣言です。

メッセージは、1 行に 1 つのフィールドタイプと 1 つのフィールド名を持つ単純なテキストファイルです。使用できるフィールドタイプは以下のとおりです。

  • int8、int16、int32、int64(および uint*)
  • float32、float64
  • string
  • 他の msg ファイル
  • 可変長 配列[]、固定長 配列[C]、有界長 配列[<= C]

これは、文字列プリミティブと他の 2 つのメッセージを使用した msg の例です。

string child_frame_id
geometry_msgs/PoseWithCovariance pose
geometry_msgs/TwistWithCovariance twist

srv ファイルは msg ファイルと似ていますが、リクエストとレスポンスの 2 つの部分があります。 両者は「-」線で区切られています。次が srv ファイルの例です。

float64 A
float64 B
---
float64 sum

上記の例では、A と B がリクエストで、Sum がレスポンスです。

msg ファイルはパッケージの msg ディレクトリに格納され、srv ファイルは srv ディレクトリに格納されます。

これらはほんの一例です。 msgおよびsrvファイルを作成する方法に関する詳細については About ROS Interfaces を参照してください。

msg パッケージを作成する

注:現在 ament_cmake パッケージのみがメッセージを生成できます(ament_python パッケージは不可)。

このチュートリアルでは rosidl_tutorials リポジトリのパッケージを使います。

cd ~/ros2_overlway_ws/src
git clone -b rosidl_tutorials https://github.com/ros2/tutorials.git
cd rosidl_tutorials/rosidl_tutorials_msgs

msg ファイルを作成する

ここでは、個人に関する情報を伝えるためのメッセージを作成します。

msg/Contact.msg を開くと表示されます。

bool FEMALE=true
bool MALE=false

string first_name
string last_name
bool gender
uint8 age
string address

このメッセージは 5 つのフィールドで構成されています。

  • first_name : string 型
  • last_name : string 型
  • gender : bool 型で、MALE でも FEMALE でもかまいません
  • age : uint8 型
  • address : 文字列型

もう 1 つステップがあります。 msg ファイルが C++、Python、その他の言語のソースコードに変換されていることを確認しましょう。

msg ファイルのビルド

package.xmlを開き、次の2行のコメントを外します。

<buildtool_depend>rosidl_default_generators</buildtool_depend>

<exec_depend>rosidl_default_runtime</exec_depend>

注記:ビルド時には "rosidl_default_generators"が必要ですが、実行時には "rosidl_default_runtime"のみが必要です。

CMakeLists.txtを開き、次の行のコメントが解除されていることを確認してください。

msg/srv ファイルからメッセージコードを生成するパッケージを探します。

find_package(rosidl_default_generators REQUIRED)

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

set(msg_files
  "msg/Contact.msg"
)

手動で .msg ファイルを追加することによって、他の .msg ファイルを追加した後に CMake がいつプロジェクトを再構成する必要があるかを保証できます。

メッセージの生成は次のように記述します。

rosidl_generate_interfaces(${PROJECT_NAME}
  ${msg_files}
)

また、必ずメッセージのランタイム依存関係をエクスポートしてください。

ament_export_dependencies(rosidl_default_runtime)

これで、msg 定義からソースファイルを生成する準備が整いました。

srv ファイルを作成する

パッケージに srv 宣言を追加します。

srv/AddTwoFloats.srv ファイルを開き、この srv 宣言を貼り付けます。

float64 a
float64 b
---
float64 sum

srv ファイルを構築する

CMakeLists.txtでサービスを宣言します。

set(srv_files
  "srv/AddTwoFloats.srv")

上述した rosidl_generate_interfaces を修正し、メッセージに加えてサービスも生成できるようにします。

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

カスタムメッセージを使う

他のパッケージから msg/srv を使う

前のセクションで作成した Contact.msg を使用して C++ ノードを書きましょう。

rosidl_tutorials パッケージに移動して、src/publish_contact.cppファイルを開きます。

#include <iostream>
#include <memory>

#include "rclcpp/rclcpp.hpp"

#include "rosidl_tutorials_msgs/msg/contact.hpp"


using namespace std::chrono_literals;

class ContactPublisher : public rclcpp::Node
{
public:
  ContactPublisher()
  : Node("address_book_publisher")
  {
    contact_publisher_ = this->create_publisher<rosidl_tutorials_msgs::msg::Contact>("contact");

    auto publish_msg = [this]() -> void {
        auto msg = std::make_shared<rosidl_tutorials_msgs::msg::Contact>();

        msg->first_name = "John";
        msg->last_name = "Doe";
        msg->age = 30;
        msg->gender = msg->MALE;
        msg->address = "unknown";

        std::cout << "Publishing Contact\nFirst:" << msg->first_name <<
          "  Last:" << msg->last_name << std::endl;

        contact_publisher_->publish(msg);
      };
    timer_ = this->create_wall_timer(1s, publish_msg);
  }

private:
  rclcpp::Publisher<rosidl_tutorials_msgs::msg::Contact>::SharedPtr contact_publisher_;
  rclcpp::timer::TimerBase::SharedPtr timer_;
};


int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);

  auto publisher_node = std::make_shared<ContactPublisher>();

  rclcpp::spin(publisher_node);

  return 0;
}
コードの説明
#include "rosidl_tutorials_msgs/msg/contact.hpp"

ここでは、使用したいメッセージのヘッダーをインクルードします。

ContactPublisher()
: Node("address_book_publisher")
{

ここでノードを定義します

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

定期的にメッセージを送信する publish_msg 関数

auto msg = std::make_shared<rosidl_tutorials_msgs::msg::Contact>();

 msg->first_name = "John";
 msg->last_name = "Doe";
 msg->age = 30;
 msg->gender = msg->MALE;
 msg->address = "unknown";

連絡先メッセージを作成し、そのフィールドに入力します。

std::cout << "Publishing Contact\nFirst:" << msg->first_name <<
  "  Last:" << msg->last_name << std::endl;

contact_publisher_->publish(msg);

最後にパブリッシュします。

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

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

さぁ、ビルドしましょう!

このメッセージを使用するには、package.xmlで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)

そして最後に、メッセージパッケージを実行可能ファイルのターゲット依存関係として宣言する必要があります。

ament_target_dependencies(publish_contact
  "rclcpp"
  "rosidl_tutorials_msgs"
)

同一パッケージから msg/srv を使う

ほとんどの場合、メッセージはインタフェースパッケージ内で宣言されますが、メッセージを 1 つのパッケージ内で宣言、作成、使用できるのも便利です。

rosidl_tutorials パッケージにメッセージを作成します。 rosidl_tutorials パッケージに msg ディレクトリを作成し、そのディレクトリ内に AddressBook.msg を作成します。その msg に以下の行を記載します:

rosidl_tutorials_msgs/Contact[] address_book

ご覧のとおり、先ほど作成した連絡先メッセージに基づいてメッセージを定義します。

このメッセージを生成するには、package.xmlでこのパッケージへの依存関係を宣言する必要があります。

<build_depend>rosidl_tutorials_msgs</build_depend>

<exec_depend>rosidl_tutorials_msgs</exec_depend>

そしてCMakeLists.txtでは以下の通り:

find_package(rosidl_tutorials_msgs REQUIRED)

set(msg_files
  "msg/AddressBook.msg"
)

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

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

src / publish_address_book.cpp を開きます。

#include <iostream>
#include <memory>

#include "rclcpp/rclcpp.hpp"

#include "rosidl_tutorials/msg/address_book.hpp"
#include "rosidl_tutorials_msgs/msg/contact.hpp"

using namespace std::chrono_literals;

class AddressBookPublisher : public rclcpp::Node
{
public:
  AddressBookPublisher()
  : Node("address_book_publisher")
  {
    address_book_publisher_ =
      this->create_publisher<rosidl_tutorials::msg::AddressBook>("address_book");

    auto publish_msg = [this]() -> void {
        auto msg = std::make_shared<rosidl_tutorials::msg::AddressBook>();
        {
          rosidl_tutorials_msgs::msg::Contact contact;
          contact.first_name = "John";
          contact.last_name = "Doe";
          contact.age = 30;
          contact.gender = contact.MALE;
          contact.address = "unknown";
          msg->address_book.push_back(contact);
        }
        {
          rosidl_tutorials_msgs::msg::Contact contact;
          contact.first_name = "Jane";
          contact.last_name = "Doe";
          contact.age = 20;
          contact.gender = contact.FEMALE;
          contact.address = "unknown";
          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);
      };
    timer_ = this->create_wall_timer(1s, publish_msg);
  }

private:
  rclcpp::Publisher<rosidl_tutorials::msg::AddressBook>::SharedPtr address_book_publisher_;
  rclcpp::timer::TimerBase::SharedPtr timer_;
};


int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  auto publisher_node = std::make_shared<AddressBookPublisher>();

  rclcpp::spin(publisher_node);

  return 0;
}
コードの説明
#include "rosidl_tutorials/msg/address_book.hpp"

新しく作成した AddressBook msg のヘッダを含めます。

#include "rosidl_tutorials_msgs/msg/contact.hpp"

カスタムメッセージ kaddress_book に連絡先を追加できるようにするために Contact msg のヘッダーを含めます。

using namespace std::chrono_literals;

class AddressBookPublisher : public rclcpp::Node
{
public:
  AddressBookPublisher()
  : Node("address_book_publisher")
  {
    address_book_publisher_ =
      this->create_publisher<rosidl_tutorials::msg::AddressBook>("address_book");

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

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

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

auto msg = std::make_shared<rosidl_tutorials::msg::AddressBook>();

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

{
rosidl_tutorials_msgs::msg::Contact contact;
contact.first_name = "John";
contact.last_name = "Doe";
contact.age = 30;
contact.gender = contact.MALE;
contact.address = "unknown";
msg->address_book.push_back(person);
}
{
rosidl_tutorials_msgs::msg::Contact person;
contact.first_name = "Jane";
contact.last_name = "Doe";
contact.age = 20;
contact.gender = contact.FEMALE;
contact.address = "unknown";
msg->address_book.push_back(contact);
}

Contact メッセージを作成と値の入力をし、それらを address_book メッセージに追加します。

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

そして、定期的にメッセージを送ります。

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

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

それではビルドしましょう!CMakeLists.txtでこのノードの新しいターゲットを作成します。

add_executable(publish_address_book
  src/publish_address_book.cpp
)

ament_target_dependencies(publish_address_book
  "rclcpp"
)

同じパッケージで生成されたメッセージを使用するには、以下のcmake コードを使用する必要があります。

get_default_rmw_implementation(rmw_implementation)
find_package("${rmw_implementation}" REQUIRED)
get_rmw_typesupport(typesupport_impls "${rmw_implementation}" LANGUAGE "cpp")

foreach(typesupport_impl ${typesupport_impls})
  rosidl_target_interfaces(publish_address_book
    ${PROJECT_NAME} ${typesupport_impl}
  )
endforeach()

これは msg/srv から関連する生成コード(C++)を検出し、ターゲットがそれらに対してリンクできるようにします。

使用されているインターフェースが事前にビルドされたパッケージからのものである場合、このステップは不要となります。このCMake コードは、msg/srv がビルドされたパッケージと同一のパッケージ内でインターフェースを使用しようとしているときにのみ必要となります。

翻訳元文書

index.ros.org

関連文書

www.moriken254.com

www.moriken254.com