ROS2编程基础课程--msg/srv

Introduction to msg and srv interfaces 

msg和srv接口简介

INCOMPLETE: this is a draft of an upcoming tutorial for creating and using custom ROS interfaces. 

未完成:这是即将发布的用于创建和使用自定义ROS接口的教程的草稿。

Disclaimer: The code provided is to support the explanation, it is likely outdated and should not be expected to compile as is 

免责声明:提供的代码是为了支持解释,它可能已经过时,不应该按原样编译

  • msg: msg files are simple text files that describe the fields of a ROS message. They are used to generate source code for messages in different languages. 

msg:msg文件是描述ROS消息字段的简单文本文件。它们用于生成不同语言的消息的源代码。

  • srv: an srv file describes a service. It is composed of two parts: a request and a response. The request and response are message declarations. 

srv:srv文件描述服务。它由两部分组成:请求和响应。请求和响应是消息声明。

msgs are just simple text files with a field type and field name per line. The field types you can use are: 

msgs只是简单的文本文件,每行有一个字段类型和字段名称。可以使用的字段类型是:

  • int8, int16, int32, int64 (plus uint*) 
  • float32, float64
  • string
  • other msg files
  • variable-length array[], fixed-length array[C], bounded-length array[<=C]

Here is an example of a msg that uses a string primitive, and two other msgs: 

这是一个使用字符串和另外两个消息的msg示例:

string child_frame_id

geometry_msgs/PoseWithCovariance pose

geometry_msgs/TwistWithCovariance twist

 

srv files are just like msg files, except they contain two parts: a request and a response. The two parts are separated by a ‘—’ line. Here is an example of a srv file: 

srv文件类似msg文件,但是它们包含两部分:请求和响应。这两部分用' - '线分开。

以下是srv文件的示例:

float64 A

float64 B

---

float64 Sum

In the above example, A and B are the request, and Sum is the response. 

在上面的例子中,A和B是请求,Sum是响应。

 

msg files are stored in the msg directory of a package, and srv files are stored in the srv directory. 

msg文件存储在包的msg目录中,srv文件存储在srv目录中。

These are just simple examples. For more information about how to create msg and srv files please refer to About ROS Interfaces. 

这些只是简单的例子。有关如何创建msg和srv文件的更多信息,请参考关于ROS接口

Creating a msg package 创建一个msg包

NOTE: only ament_cmake packages can generate messages currently (not ament_python packages). 

注意:只有ament_cmake包可以生成当前的消息(不是ament_python包)。

For this tutorial we will use the packages stored in the rosidl_tutorials repository. 

在本教程中,将使用存储在rosidl_tutorials的包。

cd ~/ros2_overlway_ws/src

git clone -b rosidl_tutorials https://github.com/ros2/tutorials.git

cd rosidl_tutorials/rosidl_tutorials_msgs

 

Creating a msg file 创建一个msg文件 

Here we will create a message meant to carry information about an individual. 

在这里,将创建一条消息,用于传递有关个人的信息。

Open msg/Contact.msg and you will see: 

打开msg/Contact.msg,会看到:

bool FEMALE=true

bool MALE=false

 

string first_name

string last_name

bool gender

uint8 age

string address

This message is composed of 5 fields: 此消息由5个字段组成:

  • first_name: of type string 

first_name:类型为字符串

  • last_name: of type string 

last_name:类型为字符串

  • gender: of type bool, that can be either MALE or FEMALE 

性别:bool类型,可以是男性或女性

  • age: of type uint8 

年龄:uint8型

  • address: of type string 

地址:字符串类型

There’s one more step, though. We need to make sure that the msg files are turned into source code for C++, Python, and other languages. 

不过还有一个步骤。需要确保将msg文件转换为C ++Python和其他语言的源代码。

Building msg files 编译msg文件

Open the package.xml, and uncomment these two lines: 

打开package.xml,并取消注释这两行:

rosidl_default_generators

 

rosidl_default_runtime

 

Note that at build time, we need “rosidl_default_generators”, while at runtime, we only need “rosidl_default_runtime”. 

请注意,在编译时,我们需要“rosidl_default_generators”,而在运行时,我们只需要“rosidl_default_runtime”。

Open the CMakeLists.txt and make sure that the following lines are uncommented. 

打开CMakeLists.txt并确保取消以下行注释。

Find the package that generates message code from msg/srv files: 

找到从msg / srv文件生成消息代码的包:

find_package(rosidl_default_generators REQUIRED)

 

Declare the list of messages you want to generate: 声明要生成的消息列表:

set(msg_files

  "msg/Contact.msg"

)

 

By adding the .msg files manually, we make sure that CMake knows when it has to reconfigure the project after you add other .msg files. 

通过手动添加.msg文件,我们确保CMake知道在添加其他.msg文件后何时必须重新配置项目。

Generate the messages: 生成消息:

rosidl_generate_interfaces(${PROJECT_NAME}

  ${msg_files}

)

 

Also make sure you export the message runtime dependency: 

还要确保导出消息运行时依赖项:

 

ament_export_dependencies(rosidl_default_runtime)

 

Now you’re ready to generate source files from your msg definition. 

现在已准备好从msg定义生成源文件。

 

Creating an srv file 创建srv文件

We will now add a srv declaration to our package. 现在将向包添加srv声明。

Open the srv/AddTwoFloats.srv file and paste this srv declaration: 

打开srv / AddTwoFloats.srv文件并粘贴此srv声明:

float64 a

float64 b

---

float64 sum

 

Building srv files 编译srv文件

Declare the service in the CMakeLists.txt: 

CMakeLists.txt以下内容中声明服务:

 

set(srv_files

  "srv/AddTwoFloats.srv")

 

Modify the existing call to rosidl_generate_interfaces to generate the service in addition to the messages: 

修改现有的rosidl_generate_interfaces调用以生成除消息之外的服务:

rosidl_generate_interfaces(${PROJECT_NAME}

  ${msg_files}

  ${srv_files}

)

 

Using custom messages 使用自定义消息

Using msg/srv from other packages 从其他包使用msg/srv

Let’s write a C++ node using the Contact.msg we created in the previous section. 

使用在上一节中创建的Contact.msg编写一个C ++节点。

 

Go to the rosidl_tutorials package and open the src/publish_contact.cpp file. 

转到rosidl_tutorials包并打开src / publish_contact.cpp文件。

#include 

#include 

 

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

}

 

The code explained 代码解释

#include "rosidl_tutorials_msgs/msg/contact.hpp"

 

Here we include the header of the message that we want to use. 这里包含想要使用的消息的头文件

ContactPublisher(): Node("address_book_publisher")

{

 

Here we define a node 这里定义一个节点

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

 

A publish_msg function to send our message periodically 一个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";

 

We create a Contact message and populate its fields. 创建一个Contact消息并填充其字段。

std::cout << "Publishing Contact\nFirst:" << msg->first_name <<

  "  Last:" << msg->last_name << std::endl;

contact_publisher_->publish(msg);

 

Finally we publish it 最后发布它

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

 

Create a 1second timer to call our publish_msg function every second 

创建一个1秒的计时器,每秒调用publish_msg函数

Now let’s build it! 现在来编译吧!

To use this message we need to declare a dependency on rosidl_tutorials_msgs in the package.xml: 

要使用此消息,需要在以下内容中声明对rosidl_tutorials_msgs的依赖package.xml

rosidl_tutorials_msgs

rosidl_tutorials_msgs

 

And also in the CMakeLists.txt: 而且在CMakeLists.txt

 

find_package(rosidl_tutorials_msgs REQUIRED)

 

And finally we must declare the message package as a target dependency for the executable. 

最后,必须将消息包声明为可执行文件的目标依赖项。

ament_target_dependencies(publish_contact

  "rclcpp"

  "rosidl_tutorials_msgs"

)

 

Using msg/srv from the same package 从使用同一个包msg/srv

While most of the time messages are declared in interface packages, it can be convenient to declare, create and use messages all in the one package. 

虽然大多数时候消息是在接口包中声明的,但是在一个包中声明,创建和使用消息都很方便。

 

We will create a message in our rosidl_tutorials package. Create a msg directory in the rosidl_tutorials package and AddressBook.msg inside that directory. In that msg paste: 

在rosidl_tutorials包中创建一条消息。在rosidl_tutorials包中创建一个msg目录,在该目录中创建AddressBook.msg。在那个msg粘贴中:

rosidl_tutorials_msgs/Contact[] address_book

 

As you can see we define a message based on the Contact message we created earlier. 

根据之前创建的Contact消息定义消息。

To generate this message we need to declare a dependency on this package in the package.xml: 

要生成此消息,需要在以下内容中声明对此包的依赖package.xml

rosidl_tutorials_msgs

rosidl_tutorials_msgs

 

And in the CMakeLists.txt: 并在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

)

Now we can start writing code that uses this message. 现在可以开始编写使用此消息的代码。

Open src/publish_address_book.cpp: 打开src / publish_address_book.cpp:

#include 

#include 

 

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

}

 

The code explained 代码解释

#include "rosidl_tutorials/msg/address_book.hpp"

 

We include the header of our newly created AddressBook msg. 

包括新创建的AddressBook消息的头文件

#include "rosidl_tutorials_msgs/msg/contact.hpp"

 

Here we include the header of the Contact msg in order to be able to add contacts to our address_book. 

这里包含Contact msg的标题,以便能够将联系人添加到address_book。

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

 

We create a node and an AddressBook publisher. 创建一个节点和一个AddressBook发布器

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

 

We create a callback to publish the messages periodically 

创建一个回调来定期发布消息

 

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

 

We create an AddressBook message instance that we will later publish. 

创建一个稍后将发布的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);

}

 

We create and populate Contact messages and add them to our address_book message. 

创建并填充联系人消息并将其添加到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);

 

Finally send the message periodically. 最后定期发送消息。

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

 

Create a 1second timer to call our publish_msg function every second 

创建一个1秒的计时器,每秒调用publish_msg函数

 

Now let’s build it! We need to create a new target for this node in the CMakeLists.txt: 

现在来编译吧!需要在CMakeLists.txt以下位置为此节点创建新目标:

add_executable(publish_address_book

  src/publish_address_book.cpp)

 

ament_target_dependencies(publish_address_book

  "rclcpp")

 

In order to use the messages generated in the same package we need to use the following cmake code: 

为了使用在同一个包中生成的消息,需要使用以下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()

 

This finds the relevant generated C++ code from msg/srv and allows your target to link against them. 

这将从msg / srv中找到相关的生成的C ++代码,并允许目标链接它们。

You may have noticed that this step was not necessary when the interfaces being used were from a package that was built beforehand. This CMake code is only required when you are trying to use interfaces in the same package as that in which they are built. 

可能已经注意到,当使用的接口来自事先编译的包时,此步骤不是必需的。仅当尝试在与编译它们的包相同的包中使用接口时,才需要此CMake代码。

 

查看一下turtlesim的msg和srv。

Pose.msg

float32 x

float32 y

float32 theta

 

float32 linear_velocity

float32 angular_velocity

 

Spawn.srv

float32 x

float32 y

float32 theta

string name # Optional.  A unique name will be created and returned if this is empty

---

string name

 

熟练掌握msg、service、srv等命令的使用。

 

 

你可能感兴趣的:(ROS2)