迁移 C++ 包 [15664]

构建工具 [15665]

在ROS 2中,不再使用 catkin_makecatkin_make_isolatedcatkin 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]

    • 请改为调用``ament_package``,但是要在所有目标都注册之后调用。 [15674]

    • ament_package``的唯一有效参数是``CONFIG_EXTRAS。所有其他参数都由单独的函数处理,这些函数都需要在``ament_package``之前调用: [15675]

      • 不要使用``CATKIN_DEPENDS ...``,而是在之前调用``ament_export_dependencies(...)``。 [15676]

      • 不要使用``INCLUDE_DIRS ...``,而是在之前调用``ament_export_include_directories(...)``。 [15677]

      • 在调用``LIBRARIES ...``之前,请使用``ament_export_libraries(...)``进行传递。 [15678]

  • 将``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]

    • 第一个参数是``target_name``。如果你只构建一个库,那么它是``${PROJECT_NAME}``。 [15680]

    • 接着是相对于软件包根目录的消息文件名列表。 [15681]

      • 如果您将多次使用文件名列表,则建议为清晰起见编写一个消息文件列表,并将其传递给函数。 [15682]

    • 最后一个多值关键字参数是``generate_messages``的``DEPENDENCIES``,需要依赖消息软件包的列表。 [15683]

      rosidl_generate_interfaces(${PROJECT_NAME}
        ${msg_files}
        DEPENDENCIES std_msgs
      )
      
  • 删除所有*devel空间*的出现。相关的CMake变量,如``CATKIN_DEVEL_PREFIX``,已不再存在。 [15684]

    • CATKIN_DEPENDSDEPENDS 参数被传递给新的函数 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_DESTINATIONlib [15689]

    • CATKIN_GLOBAL_SHARE_DESTINATIONshare [15690]

    • CATKIN_PACKAGE_BIN_DESTINATIONlib/${PROJECT_NAME} [15691]

    • CATKIN_PACKAGE_INCLUDE_DESTINATIONinclude/${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]

  • 在包名称和消息数据类型之间插入子文件夹``msg`` [15709]

  • 将包含的文件名从驼峰式更改为下划线分隔 [15710]

  • 将``*.h``更改为``*.hpp`` [15711]

// 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::Time``的实例替换为``rclcpp::Time`` [15717]

  • 如果您的消息或代码使用了std_msgs::Time: [15718]

    • 将所有的std_msgs::Time实例转换为builtin_interfaces::msg::Time [15719]

    • 将所有的#include "std_msgs/time.h"转换为#include "builtin_interfaces/msg/time.hpp" [15720]

    • 将所有使用std_msgs::Time字段``nsec``的实例转换为builtin_interfaces::msg::Time字段``nanosec`` [15721]

ros::Rate的用法 [15722]

有一个等效的类型 rclcpp::Rate 对象,基本上可以替换 ros::Rate[15723]

Boost [15724]

之前由 Boost 提供的许多功能已经集成到 C++ 标准库中。因此,我们希望尽可能利用新的核心功能,并避免对 Boost 的依赖。 [15725]

共享指针 [15726]

将共享指针从 boost 转换为标准 C++,请替换以下实例: [15727]

  • #include <boost/shared_ptr.hpp> 替换为 #include <memory> [15728]

  • boost::shared_ptr 替换为 std::shared_ptr [15729]

还可能存在诸如 weak_ptr 的变体,您也希望进行转换。 [15730]

此外,建议使用 using 而不是 typedefusing 在模板逻辑中能够更好地发挥作用。有关详细信息,请参阅 这里[15731]

线程/互斥锁 [15732]

在ROS代码库中,boost::thread 中的互斥锁是另一个常见的boost部分。 [15733]

  • 将``boost::mutex::scoped_lock``替换为``std::unique_lock<std::mutex>`` [15734]

  • 将``boost::mutex``替换为``std::mutex`` [15735]

  • 将``#include <boost/thread/mutex.hpp>``替换为``#include <mutex>`` [15736]

无序映射 [15737]

替换: [15738]

  • #include <boost/unordered_map.hpp>#include <unordered_map> [15739]

  • boost::unordered_mapstd::unordered_map [15740]

函数 [15741]

替换: [15738]

  • #include <boost/function.hpp>#include <functional> [15742]

  • boost::functionstd::function [15743]

示例:将现有的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

在另一个终端中,我们使用 rosruncatkin 安装空间运行节点,再次首先获取设置文件(在这种情况下,必须是我们工作空间中的设置文件): [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()

构建 ROS 2 代码 [15798]

我们先引用一个环境设置文件(在这个例子中,我们按照 ROS 2 安装教程生成了一个位于 ~/ros2_ws 的文件),然后使用 colcon build 构建我们的软件包: [15799]

. ~/ros2_ws/install/setup.bash
cd ~/ros2_talker
colcon build

运行ROS 2节点 [15800]

因为我们将``talker``可执行文件安装到了正确的目录中,所以在从我们的安装目录中的设置文件进行源码后,我们可以通过运行以下命令来调用它: [15801]

. ~/ros2_ws/install/setup.bash
ros2 run talker talker