在ROS 2接口上扩展 [待校准@8284]
Goal目标: Learn了解更多在ROS 2中实现自定义接口的方法 [待校准@8285]
教程级别: 初学者 [Alyssa@7088]
时间: 15分钟 [Alyssa@6755]
内容
背景
在 previous tutorial 中,您学习了如何创建自定义的 msg 和 srv 界面。 [待校准@8286]
虽然最佳实践是在专用接口包中声明接口,但有时在一个包中声明、创建和使用接口会很方便。 [待校准@8287]
重新调用接口当前只能在CMake包中定义。但是,在CMake包中有Python库和节点是可能的 (使用 ament_cmake_python ),因此您可以在一个包中一起定义接口和Python节点。为了简单起见,我们将在这里使用CMake包和cnodes节点。 [待校准@8288]
本教程将重点介绍 msg 接口类型,但此处的步骤适用于所有接口类型。 [待校准@8289]
先决条件
我们假设在完成本教程之前,您已经阅读了 创建自定义ROS 2 msg 和 srv 文件 [待校准@7179] 教程中的基础知识。 [待校准@8290]
你应该有 ROS 2 installed , workspace, and an understanding of creating packages 。 [待校准@8291]
和往常一样,不要忘记在你打开的每个新航站楼都要 source ROS 2 。 [待校准@8292]
任务
1创建包
在工作区 src
目录中,创建一个 more_interfaces
包,并在其中为 msg 文件创建一个文件夹: [待校准@8293]
ros2 pkg create --build-type ament_cmake more_interfaces
mkdir more_interfaces/msg
2创建一个 msg 文件 [待校准@8294]
在 more_interfaces/msg
内部,创建一个新文件 AddressBook.msg
[待校准@8295]
粘贴以下代码以创建用于携带个人信息的消息: [待校准@8296]
bool FEMALE=true
bool MALE=false
string first_name
string last_name
bool gender
uint8 age
string address
此消息由5个字段组成: [待校准@8297]
名字: string类型 [待校准@8298]
姓氏: string类型 [待校准@8299]
性别: bool类型,可以是男性也可以是女性 [待校准@8300]
年龄: uint8类型 [待校准@8301]
地址: string类型 [待校准@8302]
请注意,可以为消息定义中的字段设置默认值。有关自定义界面的更多方法,请参见 关于ROS 2接口 [待校准@10102] 。 [待校准@8303]
接下来,我们需要确保将 msg 文件转换为c ++ 、Python和其他语言的源文件。 [待校准@8304]
2.1建立一个 msg 文件 [待校准@8305]
打开 package.xml
,并添加以下几行: [待校准@8306]
<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
。 [待校准@8307]
打开 CMakeLists.txt
并添加以下几行: [待校准@8308]
查找从msg/srv文件生成消息代码的包: [待校准@8309]
find_package(rosidl_default_generators REQUIRED)
声明要生成的消息列表: [待校准@8310]
set(msg_files
"msg/AddressBook.msg"
)
通过添加。msg文件手动,我们确保CMake知道在您添加其他文件后何时必须重新配置项目。msg文件。 [待校准@8311]
生成消息: [待校准@8312]
rosidl_generate_interfaces(${PROJECT_NAME}
${msg_files}
)
另外,请确保导出消息运行时依赖项: [待校准@8313]
ament_export_dependencies(rosidl_default_runtime)
现在,您可以根据 msg 定义生成源文件了。我们现在将跳过编译步骤,因为我们将在下面的步骤4中一起完成。 [待校准@8314]
2.2 (额外) 设置多个接口 [待校准@8315]
注解
您可以使用 set
整齐地列出您的所有界面: [待校准@8316]
set(msg_files
"msg/Message1.msg"
"msg/Message2.msg"
# etc
)
set(srv_files
"srv/Service1.srv"
"srv/Service2.srv"
# etc
)
同时生成所有列表,如下所示: [待校准@8317]
rosidl_generate_interfaces(${PROJECT_NAME}
${msg_files}
${srv_files}
)
3使用同一包的接口 [待校准@8318]
现在,我们可以开始编写使用此消息的代码。 [待校准@8319]
在 more_interfaces/src
中创建一个调用 publish_address_book.cpp
的文件,并粘贴以下代码: [待校准@8320]
#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.age = 30;
message.gender = message.MALE;
message.address = "unknown";
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解释的代码 [待校准@8321]
#include "more_interfaces/msg/address_book.hpp"
包括我们新创建的 AddressBook.msg
的标题。 [待校准@8322]
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");
创建一个节点和一个 AddressBook
发布服务器。 [待校准@8323]
auto publish_msg = [this]() -> void {
创建一个调用back以在调用y期间发布消息。 [待校准@8324]
auto message = more_interfaces::msg::AddressBook();
创建一个 AddressBook
消息实例,我们稍后将发布该实例。 [待校准@8325]
message.first_name = "John";
message.last_name = "Doe";
message.age = 30;
message.gender = message.MALE;
message.address = "unknown";
填充 AddressBook
字段。 [待校准@8326]
std::cout << "Publishing Contact\nFirst:" << message.first_name <<
" Last:" << message.last_name << std::endl;
this->address_book_publisher_->publish(message);
最后发送消息周期调用y。 [待校准@8327]
timer_ = this->create_wall_timer(1s, publish_msg);
创建一个1秒计时器,每秒调用我们的 publish_msg
函数。 [待校准@8328]
3.2建立出版商 [待校准@8329]
我们需要在 CMakeLists.txt
中为该节点创建一个新目标: [待校准@8330]
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连接接口 [待校准@8331]
为了使用在同一包中生成的消息,我们需要使用以下CMake代码: [待校准@8332]
rosidl_target_interfaces(publish_address_book
${PROJECT_NAME} "rosidl_typesupport_cpp")
这可以从 AddressBook.msg
中找到相关生成的ccode代码,并允许您的目标与之链接。 [待校准@8333]
您可能已经注意到,当使用的接口来自单独构建的包时,此步骤不是必需的。仅当您希望在与使用它们的包相同的包中使用接口时,才需要此CMake代码。 [待校准@8334]
4试试看 [待校准@8335]
返回工作区的根目录以构建包: [待校准@8336]
cd ~/dev_ws
colcon build --packages-up-to more_interfaces
cd ~/dev_ws
colcon build --packages-up-to more_interfaces
cd /dev_ws
colcon build --merge-install --packages-up-to more_interfaces
然后源文件工作区并运行发布服务器: [待校准@8337]
. install/local_setup.bash
ros2 run more_interfaces publish_address_book
. install/local_setup.bash
ros2 run more_interfaces publish_address_book
call install/local_setup.bat
ros2 run more_interfaces publish_address_book
或使用Powershell: [待校准@8338]
install/local_setup.ps1
ros2 run more_interfaces publish_address_book
你应该看到出版商转发你定义的 msg ,包括你在 publish_address_book.cpp
中设置的值。 [待校准@8339]
要确认消息正在 address_book
话题上发布,请打开另一个终端,源文件工作区,并调用 topic echo
: [待校准@8340]
. install/setup.bash
ros2 topic echo /address_book
. install/setup.bash
ros2 topic echo /address_book
call install/setup.bat
ros2 topic echo /address_book
或使用Powershell: [待校准@8338]
install/setup.ps1
ros2 topic echo /address_book
在本教程中,我们不会创建订阅者,但您可以尝试自己编写一个订阅者进行练习 (使用: doc:'。/写一个简单的Cpp出版商和订阅者来帮助)。 [待校准@8341]
5 (额外) 使用现有接口定义 [待校准@8342]
注解
您可以在新接口定义中使用现有接口定义。例如,假设有一个名为 Contact.msg
的消息属于现有的名为 rosidl_tutorials_msgs
的ROS 2包。假设它的定义与我们之前定制的 AddressBook.msg
界面相同。 [待校准@8343]
在这种情况下,您可以将 AddressBook.msg
(包中 * 与 * 您的节点的接口) 定义为类型 Contact
(包中 * 单独 * 的接口)。您甚至可以将 AddressBook.msg
定义为 Contact
类型的 * 数组 *,如下所示: [待校准@8344]
rosidl_tutorials_msgs/Contact[] address_book
要生成此消息,您需要声明对 “contact.Msg” 包的依赖, rosidl_tutorials_msgs
,在 package.xml
中: [待校准@8345]
<build_depend>rosidl_tutorials_msgs</build_depend>
<exec_depend>rosidl_tutorials_msgs</exec_depend>
在 CMakeLists.txt
中: [待校准@8346]
find_package(rosidl_tutorials_msgs REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
${msg_files}
DEPENDENCIES rosidl_tutorials_msgs
)
你还需要包括标题 Contact.msg
你发行商节点为能增加 contacts
的 address_book
。 [待校准@8347]
#include "rosidl_tutorials_msgs/msg/contact.hpp"
您可以将调用更改回如下内容: [待校准@8348]
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.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);
};
建立和运行这些变化将显示按预期定义的 msg ,以及上面定义的msgs阵列。 [待校准@8349]
总结
在本教程中,您尝试了用于定义接口的不同字段类型,然后在使用它的同一包中构建了一个接口。 [待校准@8350]
您还学习了如何使用另一个接口作为字段类型,以及使用该功能所需的 package.xml
、 CMakeLists.txt
和 “# 包含” 语句。 [待校准@8351]
下一步
接下来,您将创建一个带有自定义参数的简单ROS 2包,您将学习从launch文件中设置该参数。同样,你可以选择用 C++ 或 Python 写。 [待校准@8352]