迁移 C++ 包示例

此示例展示了如何将示例 C++ 包从 ROS 1 迁移到 ROS 2。

先决条件

您需要一个可运行的 ROS 2 安装,例如 ROS rolling

ROS 1 代码

假设您有一个名为“talker”的 ROS 1 包,它在一个名为“talker”的节点中使用“roscpp”。

此包位于 catkin 工作区中,位于“~/ros1_talker”处。

您的 ROS 1 工作区具有以下目录布局:

$ cd ~/ros1_talker
$ find .
.
./src
./src/talker
./src/talker/package.xml
./src/talker/CMakeLists.txt
./src/talker/talker.cpp

这些文件包含以下内容:

src/talker/package.xml:

<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format2.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="2">
  <name>talker</name>
  <version>0.0.0</version>
  <description>talker</description>
  <maintainer email="gerkey@example.com">Brian Gerkey</maintainer>
  <license>Apache-2.0</license>
  <buildtool_depend>catkin</buildtool_depend>
  <depend>roscpp</depend>
  <depend>std_msgs</depend>
</package>

src/talker/CMakeLists.txt:

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:

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

让我们首先创建一个新的工作空间:

mkdir ~/ros2_talker
cd ~/ros2_talker

我们将把 ROS 1 包中的源树复制到该工作区中,在那里我们可以对其进行修改:

mkdir src
cp -a ~/ros1_talker/src/talker src

现在我们将修改节点中的 C++ 代码。 ROS 2 C++ 库名为“rclcpp”,它提供的 API 与“roscpp”提供的 API 不同。 这两个库的概念非常相似,这使得更改相当简单。 包含的标题 ~~~~~~~~~~~~~~~

我们需要包含“rclcpp/rclcpp.hpp”,以代替“ros/ros.h”,它使我们能够访问“roscpp”库 API:

//#include "ros/ros.h"
#include "rclcpp/rclcpp.hpp"

为了获取“std_msgs/String”消息定义,我们需要包含“std_msgs/msg/string.hpp”来代替“std_msgs/String.h”:

//#include "std_msgs/String.h"
#include "std_msgs/msg/string.hpp"

更改 C++ 库调用

我们不会将节点名称传递给库初始化调用,而是先进行初始化,然后将节点名称传递给节点对象的创建:

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

为了进一步控制消息传递的处理方式,可以传入服务质量(QoS)配置文件。

默认配置文件为``rmw_qos_profile_default``。

有关更多详细信息,请参阅 `设计文档<https://design.ros2.org/articles/qos.html>`__ 和:doc:概念概述

在命名空间中,传出消息的创建有所不同:

//  std_msgs::String msg;
  std_msgs::msg::String msg;

代替“ros::ok()”,我们调用“rclcpp::ok()”:

//  while (ros::ok())
  while (rclcpp::ok())

在发布循环中,我们像以前一样访问“数据”字段:

msg.data = ss.str();

要打印控制台消息,我们不使用“ROS_INFO()”,而是使用“RCLCPP_INFO()”及其各种同类函数。主要区别在于“RCLCPP_INFO()”将 Logger 对象作为第一个参数。

//    ROS_INFO("%s", msg.data.c_str());
    RCLCPP_INFO(node->get_logger(), "%s\n", msg.data.c_str());

将发布调用更改为使用“->”运算符而不是“。”。

//    chatter_pub.publish(msg);
    chatter_pub->publish(msg);

旋转(即让通信系统处理任何待处理的传入/传出消息)的不同之处在于,调用现在将节点作为参数:

//    ros::spinOnce();
    rclcpp::spin_some(node);

使用 rate 对象休眠没有变化。

综合起来,新的“talker.cpp”如下所示:

#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);
    chatter_pub->publish(msg);
//    ros::spinOnce();
    rclcpp::spin_some(node);
    loop_rate.sleep();
  }
  return 0;
}

更改 package.xml

ROS 2 软件包使用来自 ament_cmake_ros 的 CMake 函数和宏,而不是 catkin。 删除对 catkin 的依赖:

<!-- delete this -->
<buildtool_depend>catkin</buildtool_depend>`

添加对“ament_cmake_ros”的新依赖项:

<buildtool_depend>ament_cmake_ros</buildtool_depend>

ROS 2 C++ 库使用 rclcpp 而不是 roscpp

删除对 roscpp 的依赖:

<!-- delete this -->
<depend>roscpp</depend>

添加对“rclcpp”的依赖:

<depend>rclcpp</depend>

添加“<export>”部分来告诉 colcon 该包是一个“ament_cmake”包而不是“catkin”包。

<export>
  <build_type>ament_cmake</build_type>
</export>

你的“package.xml”现在看起来像这样:

<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format2.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="2">
  <name>talker</name>
  <version>0.0.0</version>
  <description>talker</description>
  <maintainer email="gerkey@example.com">Brian Gerkey</maintainer>
  <license>Apache-2.0</license>
  <buildtool_depend>ament_cmake</buildtool_depend>
  <depend>rclcpp</depend>
  <depend>std_msgs</depend>
  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>

更改 CMake 代码

需要较新版本的 CMake,以便“ament_cmake”函数正常工作。

cmake_minimum_required(VERSION 3.14.4)

使用与 REP 2000 中目标 ROS 发行版使用的版本相匹配的较新的 C++ 标准。 如果您使用的是 C++17,则在 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 …)”调用替换为针对每个依赖项的单独调用。

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)

删除对“catkin_package()”的调用。 在“CMakeLists.txt”的底部添加对“ament_package()”的调用。

ament_package()

让“target_link_libraries”调用“rclcpp”和“std_msgs”提供的现代 CMake 目标。

target_link_libraries(talker PUBLIC
  rclcpp::rclcpp
  ${std_msgs_TARGETS})

删除对“include_directories()”的调用。 在“add_executable(talker talker.cpp)”下面添加对“target_include_directories()”的调用。 不要将“rclcpp_INCLUDE_DIRS”等变量传递到“target_include_directories()”中。 已通过使用现代 CMake 目标调用“target_link_libraries()”来处理包含目录。

target_include_directories(talker PUBLIC
   "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
   "$<INSTALL_INTERFACE:include/${PROJECT_NAME}>")

将调用更改为“install()”,以便“talker”可执行文件安装到特定于项目的目录中。

install(TARGETS talker
  DESTINATION lib/${PROJECT_NAME})

新的“CMakeLists.txt”如下所示:

cmake_minimum_required(VERSION 3.14.4)
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(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
add_executable(talker talker.cpp)
target_include_directories(talker PUBLIC
   "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
   "$<INSTALL_INTERFACE:include/${PROJECT_NAME}>")
target_link_libraries(talker PUBLIC
  rclcpp::rclcpp
  ${std_msgs_TARGETS})
install(TARGETS talker
  DESTINATION lib/${PROJECT_NAME})
ament_package()

构建 ROS 2 代码

我们获取一个环境设置文件(在本例中是按照 ROS 2 安装教程生成的文件,它在 ~/ros2_ws 中构建),然后我们使用 colcon build 构建我们的包:

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

运行 ROS 2 节点

因为我们将 talker 可执行文件安装到了正确的目录中,所以在从安装树中获取安装文件后,我们可以通过运行以下命令来调用它:

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

结论

您已经了解了如何将示例 C++ ROS 1 包迁移到 ROS 2。 使用 迁移 C++ 包参考页 来帮助您将自己的 C++ 包从 ROS 1 迁移到 ROS 2。