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,并取消注释这两行:
<buildtool_depend>rosidl_default_generators</buildtool_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
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 <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"
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:
<build_depend>rosidl_tutorials_msgs</build_depend>
<exec_depend>rosidl_tutorials_msgs</exec_depend>
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:
<build_depend>rosidl_tutorials_msgs</build_depend>
<exec_depend>rosidl_tutorials_msgs</exec_depend>
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 <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"
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等命令的使用。