迁移 C++ 包 [15664]
目录 []
构建工具 [15665]
在ROS 2中,不再使用 catkin_make
、catkin_make_isolated
或 catkin build
,而是使用命令行工具 colcon 来构建和安装一组包。请查看 初学者教程 以开始使用 colcon
。 [15666]
构建系统 [15667]
ROS 2 中的构建系统称为 ament。Ament 基于 CMake 构建:ament_cmake
提供了用于更轻松编写 CMakeLists.txt
文件的 CMake 函数。 [15668]
更新 CMakeLists.txt 使用 ament_cmake [15669]
将以下更改应用于使用``ament_cmake``而不是``catkin``: [15670]
在``package.xml``文件的导出部分中设置构建类型: [15671]
<export> <build_type>ament_cmake</build_type> </export>
将``find_package``调用替换为``catkin``,``COMPONENTS``改为: [15672]
find_package(ament_cmake REQUIRED) find_package(component1 REQUIRED) # ... find_package(componentN REQUIRED)
移动并更新``catkin_package``调用为: [15673]
将``add_message_files``、``add_service_files``和``generate_messages``的调用替换为`rosidl_generate_interfaces <https://github.com/ros2/rosidl/blob/humble/rosidl_cmake/cmake/rosidl_generate_interfaces.cmake>`__。 [15679]
删除所有*devel空间*的出现。相关的CMake变量,如``CATKIN_DEVEL_PREFIX``,已不再存在。 [15684]
CATKIN_DEPENDS
和DEPENDS
参数被传递给新的函数 ament_export_dependencies。 [15685]CATKIN_GLOBAL_BIN_DESTINATION
:bin
[15686]CATKIN_GLOBAL_INCLUDE_DESTINATION
:include
[15687]CATKIN_GLOBAL_LIB_DESTINATION
:lib
[15688]CATKIN_GLOBAL_LIBEXEC_DESTINATION
:lib
[15689]CATKIN_GLOBAL_SHARE_DESTINATION
:share
[15690]CATKIN_PACKAGE_BIN_DESTINATION
:lib/${PROJECT_NAME}
[15691]CATKIN_PACKAGE_INCLUDE_DESTINATION
:include/${PROJECT_NAME}
[15692]CATKIN_PACKAGE_LIB_DESTINATION
:lib
[15693]CATKIN_PACKAGE_SHARE_DESTINATION
:share/${PROJECT_NAME}
[15694]
单元测试 [15695]
如果您正在使用 gtest: [15696]
将 CATKIN_ENABLE_TESTING
替换为 BUILD_TESTING
。将 catkin_add_gtest
替换为 ament_add_gtest
。 [15697]
- if (CATKIN_ENABLE_TESTING)
- find_package(GTest REQUIRED) # or rostest
- include_directories(${GTEST_INCLUDE_DIRS})
- catkin_add_gtest(${PROJECT_NAME}-some-test src/test/some_test.cpp)
- target_link_libraries(${PROJECT_NAME}-some-test
- ${PROJECT_NAME}_some_dependency
- ${catkin_LIBRARIES}
- ${GTEST_LIBRARIES})
- endif()
+ if (BUILD_TESTING)
+ find_package(ament_cmake_gtest REQUIRED)
+ ament_add_gtest(${PROJECT_NAME}-some-test src/test/test_something.cpp)
+ ament_target_dependencies(${PROJECT_NAME)-some-test
+ "rclcpp"
+ "std_msgs")
+ target_link_libraries(${PROJECT_NAME}-some-test
+ ${PROJECT_NAME}_some_dependency)
+ endif()
在你的 package.xml
中添加 <test_depend>ament_cmake_gtest</test_depend>
。 [15698]
- <test_depend>rostest</test_depend>
+ <test_depend>ament_cmake_gtest</test_depend>
代码检查工具 [15699]
在 ROS 2 中,我们努力保持干净的代码,并使用代码检查工具。不同语言的代码风格在我们的 开发者指南 中有定义。 [15700]
如果您要从头开始创建项目,建议遵循代码风格指南,并通过在 if(BUILD_TESTING)
下方添加以下行来启用自动代码检查单元测试: [15701]
find_package(ament_lint_auto REQUIRED)
ament_lint_auto_find_test_dependencies()
您还需要将以下依赖项添加到您的 package.xml
文件中: [15702]
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
更新源代码 [15703]
消息、服务和动作 [15704]
ROS 2的消息、服务和动作的命名空间在包名后使用子命名空间(分别是``msg``、srv``或``action
)。因此,一个包含语句的形式如下:#include <my_interfaces/msg/my_message.hpp>
。C++类型的命名为:my_interfaces::msg::MyMessage
。 [15705]
共享指针类型在消息结构体中作为typedef提供:my_interfaces::msg::MyMessage::SharedPtr``以及``my_interfaces::msg::MyMessage::ConstSharedPtr
。 [15706]
更多详细信息请参阅有关`生成的C++接口 <https://design.ros2.org/articles/generated_interfaces_cpp.html>`__的文章。 [15707]
迁移需要通过以下方式更改包含: [15708]
// ROS 1 style is in comments, ROS 2 follows, uncommented.
// # include <geometry_msgs/PointStamped.h>
#include <geometry_msgs/msg/point_stamped.hpp>
// geometry_msgs::PointStamped point_stamped;
geometry_msgs::msg::PointStamped point_stamped;
迁移需要在所有实例中插入``msg``命名空间的代码。 [15712]
使用服务对象 [15713]
ROS 2中的服务回调函数不具有布尔返回值。建议在失败时抛出异常,而不是返回false。 [15714]
// ROS 1 style is in comments, ROS 2 follows, uncommented.
// #include "nav_msgs/GetMap.h"
#include "nav_msgs/srv/get_map.hpp"
// bool service_callback(
// nav_msgs::GetMap::Request & request,
// nav_msgs::GetMap::Response & response)
void service_callback(
const std::shared_ptr<nav_msgs::srv::GetMap::Request> request,
std::shared_ptr<nav_msgs::srv::GetMap::Response> response)
{
// ...
// return true; // or false for failure
}
ros::Time的用法 [15715]
关于``ros::Time``的用法: [15716]
示例:将现有的ROS 1 包转换为 ROS 2 [15744]
假设我们有一个名为``talker``的简单ROS 1包,在一个名为``talker``的节点中使用了``roscpp``。该包位于catkin工作空间中,位置为``~/ros1_talker``。 [15745]
ROS 1代码 [15746]
这是我们的catkin工作空间的目录布局: [15747]
$ cd ~/ros1_talker
$ find .
.
./src
./src/talker
./src/talker/package.xml
./src/talker/CMakeLists.txt
./src/talker/talker.cpp
以下是这三个文件的内容: [15748]
src/talker/package.xml
: [15749]
<package>
<name>talker</name>
<version>0.0.0</version>
<description>talker</description>
<maintainer email="gerkey@osrfoundation.org">Brian Gerkey</maintainer>
<license>Apache 2.0</license>
<buildtool_depend>catkin</buildtool_depend>
<build_depend>roscpp</build_depend>
<build_depend>std_msgs</build_depend>
<run_depend>roscpp</run_depend>
<run_depend>std_msgs</run_depend>
</package>
src/talker/CMakeLists.txt
: [15750]
cmake_minimum_required(VERSION 2.8.3)
project(talker)
find_package(catkin REQUIRED COMPONENTS roscpp std_msgs)
catkin_package()
include_directories(${catkin_INCLUDE_DIRS})
add_executable(talker talker.cpp)
target_link_libraries(talker ${catkin_LIBRARIES})
install(TARGETS talker
RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION})
src/talker/talker.cpp
: [15751]
#include <sstream>
#include "ros/ros.h"
#include "std_msgs/String.h"
int main(int argc, char **argv)
{
ros::init(argc, argv, "talker");
ros::NodeHandle n;
ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
ros::Rate loop_rate(10);
int count = 0;
std_msgs::String msg;
while (ros::ok())
{
std::stringstream ss;
ss << "hello world " << count++;
msg.data = ss.str();
ROS_INFO("%s", msg.data.c_str());
chatter_pub.publish(msg);
ros::spinOnce();
loop_rate.sleep();
}
return 0;
}
构建 ROS 1 代码 [15752]
我们会源码一个环境设置文件(在此示例中是 Noetic 使用 bash 的情况),然后使用 catkin_make install
构建我们的包: [15753]
. /opt/ros/noetic/setup.bash
cd ~/ros1_talker
catkin_make install
运行 ROS 1 节点 [15754]
如果还没有运行roscore,我们首先从我们的``catkin``安装目录中的设置文件开始运行``roscore``(系统设置文件位于``/opt/ros/noetic/setup.bash``也可以使用在这里): [15755]
. ~/ros1_talker/install/setup.bash
roscore
在另一个终端中,我们使用 rosrun
从 catkin
安装空间运行节点,再次首先获取设置文件(在这种情况下,必须是我们工作空间中的设置文件): [15756]
. ~/ros1_talker/install/setup.bash
rosrun talker talker
迁移到ROS 2 [15757]
我们首先创建一个新的工作空间来进行工作: [15758]
mkdir ~/ros2_talker
cd ~/ros2_talker
我们将从我们的ROS 1软件包中复制源代码树到该工作空间中,在那里我们可以进行修改: [15759]
mkdir src
cp -a ~/ros1_talker/src/talker src
现在我们将修改节点中的C++代码。ROS 2的C++库名为``rclcpp``,其提供的API与``roscpp``提供的API不同。这两个库之间的概念非常相似,这使得修改相对来说相当简单。 [15760]
包含的头文件 [15761]
我们需要包含``rclcpp/rclcpp.hpp``来替代``ros/ros.h``,后者提供了对``roscpp``库API的访问权限,而前者则提供了对``rclcpp``库API的访问权限: [15762]
//#include "ros/ros.h"
#include "rclcpp/rclcpp.hpp"
要获取``std_msgs/String``消息定义,我们需要包含``std_msgs/msg/string.hpp``,而不是``std_msgs/String.h``: [15763]
//#include "std_msgs/String.h"
#include "std_msgs/msg/string.hpp"
更改C++库调用 [15764]
与将节点名称传递给库初始化调用不同,我们首先进行初始化,然后将节点名称传递给节点对象的创建: [15765]
// ros::init(argc, argv, "talker");
// ros::NodeHandle n;
rclcpp::init(argc, argv);
auto node = rclcpp::Node::make_shared("talker");
发布者和速率对象的创建看起来非常相似,只是命名空间和方法的名称有一些变化。 [15766]
// ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
// ros::Rate loop_rate(10);
auto chatter_pub = node->create_publisher<std_msgs::msg::String>("chatter",
1000);
rclcpp::Rate loop_rate(10);
为了进一步控制消息传递的处理方式,可以传递一个质量服务(QoS
)配置文件。默认配置文件是``rmw_qos_profile_default``。更多详细信息,请参阅 设计文档 和 概念概述。 [15767]
在命名空间中,对传出消息的创建方式有所不同: [15768]
// std_msgs::String msg;
std_msgs::msg::String msg;
我们用 rclcpp::ok()
替代了 ros::ok()
: [15769]
// while (ros::ok())
while (rclcpp::ok())
在发布循环中,我们仍然像之前一样访问 data
字段: [15770]
msg.data = ss.str();
要打印控制台消息,不再使用``ROS_INFO()``,而是使用``RCLCPP_INFO()``及其各种相关函数。关键区别在于``RCLCPP_INFO()``将Logger对象作为第一个参数。 [15771]
// ROS_INFO("%s", msg.data.c_str());
RCLCPP_INFO(node->get_logger(), "%s\n", msg.data.c_str());
发布消息的方式与之前相同: [15772]
chatter_pub->publish(msg);
循环(即让通信系统处理任何待处理的传入/传出消息)的方式不同,调用现在将节点作为参数: [15773]
// ros::spinOnce();
rclcpp::spin_some(node);
使用rate对象进行休眠的方式保持不变。 [15774]
将所有内容组合在一起,新的 talker.cpp
如下所示: [15775]
#include <sstream>
// #include "ros/ros.h"
#include "rclcpp/rclcpp.hpp"
// #include "std_msgs/String.h"
#include "std_msgs/msg/string.hpp"
int main(int argc, char **argv)
{
// ros::init(argc, argv, "talker");
// ros::NodeHandle n;
rclcpp::init(argc, argv);
auto node = rclcpp::Node::make_shared("talker");
// ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
// ros::Rate loop_rate(10);
auto chatter_pub = node->create_publisher<std_msgs::msg::String>("chatter", 1000);
rclcpp::Rate loop_rate(10);
int count = 0;
// std_msgs::String msg;
std_msgs::msg::String msg;
// while (ros::ok())
while (rclcpp::ok())
{
std::stringstream ss;
ss << "hello world " << count++;
msg.data = ss.str();
// ROS_INFO("%s", msg.data.c_str());
RCLCPP_INFO(node->get_logger(), "%s\n", msg.data.c_str());
chatter_pub->publish(msg);
// ros::spinOnce();
rclcpp::spin_some(node);
loop_rate.sleep();
}
return 0;
}
修改 package.xml
[15776]
ROS 2 使用名为 ament_cmake
的较新版本 catkin
,我们在 buildtool_depend
标签中进行了指定: [15777]
<!-- <buildtool_depend>catkin</buildtool_depend> -->
<buildtool_depend>ament_cmake</buildtool_depend>
在我们的构建依赖项中,我们使用``rclcpp``代替了``roscpp``,它提供了我们使用的C++ API。 [15778]
<!-- <build_depend>roscpp</build_depend> -->
<build_depend>rclcpp</build_depend>
在运行时依赖项中,我们进行了相同的修改,并且还将``run_depend``标签更新为``exec_depend``标签(这是升级到软件包格式版本2的一部分): [15779]
<!-- <run_depend>roscpp</run_depend> -->
<exec_depend>rclcpp</exec_depend>
<!-- <run_depend>std_msgs</run_depend> -->
<exec_depend>std_msgs</exec_depend>
在ROS 1中,我们使用``<depend>``来简化指定编译时和运行时的依赖关系。在ROS 2中,我们可以做同样的事情: [15780]
<depend>rclcpp</depend>
<depend>std_msgs</depend>
我们还需要告诉构建工具我们是哪种类型的软件包,以便它知道如何构建我们。因为我们使用``ament``和CMake,所以我们添加以下行来声明我们的构建类型为``ament_cmake``: [15781]
<export>
<build_type>ament_cmake</build_type>
</export>
将所有内容放在一起,我们的``package.xml``现在看起来像这样: [15782]
<!-- <package> -->
<package format="2">
<name>talker</name>
<version>0.0.0</version>
<description>talker</description>
<maintainer email="gerkey@osrfoundation.org">Brian Gerkey</maintainer>
<license>Apache License 2.0</license>
<!-- <buildtool_depend>catkin</buildtool_depend> -->
<buildtool_depend>ament_cmake</buildtool_depend>
<!-- <build_depend>roscpp</build_depend> -->
<!-- <run_depend>roscpp</run_depend> -->
<!-- <run_depend>std_msgs</run_depend> -->
<depend>rclcpp</depend>
<depend>std_msgs</depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>
修改CMake代码 [15783]
ROS 2依赖于更高版本的CMake: [15784]
#cmake_minimum_required(VERSION 2.8.3)
cmake_minimum_required(VERSION 3.5)
ROS 2 依赖于 C++17 标准。取决于您使用的编译器,可能默认情况下未启用对 C++17 的支持。通过在文件顶部附近添加以下行来显式启用对 C++17 的支持: [15785]
set(CMAKE_CXX_STANDARD 17)
在所有平台上工作的首选方式如下: [15786]
if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 17)
endif()
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
使用 catkin
,我们通过将要构建的包作为 COMPONENTS
参数传递给初始查找 catkin
本身的命令来指定要构建的包。对于 ament_cmake
,我们逐个查找每个包,从 ament_cmake
开始: [15787]
#find_package(catkin REQUIRED COMPONENTS roscpp std_msgs)
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
系统依赖项与以前一样可以找到: [15788]
find_package(Boost REQUIRED COMPONENTS system filesystem thread)
我们使用 catkin_package()
来自动生成像 CMake 配置文件这样的东西,以供使用我们包的其他包使用。与构建目标之前的调用相反,现在我们在目标之后调用类似的 ament_package()
: [15789]
# catkin_package()
# At the bottom of the file:
ament_package()
唯一需要手动包含的目录是本地目录和不是 ament 包的依赖项: [15790]
#include_directories(${catkin_INCLUDE_DIRS})
include_directories(include ${Boost_INCLUDE_DIRS})
更好的选择是为每个目标单独指定包含目录,而不是为所有目标包含所有目录: [15791]
target_include_directories(target PUBLIC include ${Boost_INCLUDE_DIRS})
类似于我们单独找到每个依赖包的方式,我们需要将每个依赖包链接到构建目标上。对于 ament 包作为依赖包的链接,不使用 target_link_libraries()
,而是使用 ament_target_dependencies()
是处理构建标志更简洁和更彻底的方式。它自动处理 _INCLUDE_DIRS
中定义的包含目录和 _LIBRARIES
中定义的链接库。 [15792]
#target_link_libraries(talker ${catkin_LIBRARIES})
ament_target_dependencies(talker
rclcpp
std_msgs)
要与非ament软件包(例如系统依赖项,如“Boost”或在同一个“CMakeLists.txt”中构建的库)进行链接,请使用“target_link_libraries()”: [15793]
target_link_libraries(target ${Boost_LIBRARIES})
对于安装,catkin
定义了变量,如 CATKIN_PACKAGE_BIN_DESTINATION
。使用 ament_cmake
,我们只需给出相对于安装根目录的路径: [15794]
#install(TARGETS talker
# RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION})
install(TARGETS talker
DESTINATION lib/${PROJECT_NAME})
可选地,我们可以安装并导出包含的目录给下游软件包: [15795]
install(DIRECTORY include/
DESTINATION include)
ament_export_include_directories(include)
可选地,我们可以为下游软件包导出依赖项: [15796]
ament_export_dependencies(std_msgs)
将所有内容放在一起,新的 CMakeLists.txt
如下所示: [15797]
#cmake_minimum_required(VERSION 2.8.3)
cmake_minimum_required(VERSION 3.5)
project(talker)
if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 17)
endif()
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
#find_package(catkin REQUIRED COMPONENTS roscpp std_msgs)
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
#catkin_package()
#include_directories(${catkin_INCLUDE_DIRS})
include_directories(include)
add_executable(talker talker.cpp)
#target_link_libraries(talker ${catkin_LIBRARIES})
ament_target_dependencies(talker
rclcpp
std_msgs)
#install(TARGETS talker
# RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION})
install(TARGETS talker
DESTINATION lib/${PROJECT_NAME})
install(DIRECTORY include/
DESTINATION include)
ament_export_include_directories(include)
ament_export_dependencies(std_msgs)
ament_package()