C++ 软件包迁移示例
本示例展示了如何将一个 C++ 示例软件包从 ROS 1 迁移到 ROS 2。
需要安装好可正常使用的 ROS 2 环境,例如 ROS DISTRO。
ROS 1 代码
Section titled “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
Section titled “迁移到 ROS 2”首先创建一个新的工作空间:
mkdir ~/ros2_talkercd ~/ros2_talker将 ROS 1 软件包的源代码复制到该工作空间中,方便后续修改:
mkdir srccp -a ~/ros1_talker/src/talker src接下来修改节点中的 C++ 代码。
ROS 2 的 C++ 库(即 rclcpp)提供了与 roscpp 不同的 API,但两者的概念非常相似,因此迁移起来并不复杂。
包含的头文件
Section titled “包含的头文件”用 rclcpp/rclcpp.hpp(用于访问 rclcpp 库 API)替换 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++ 库调用
Section titled “更改 C++ 库调用”ROS 2 中不再将节点名称传给初始化调用,而是先执行初始化,再在创建节点对象时传入节点名称:
// ros::init(argc, argv, "talker");// ros::NodeHandle n; rclcpp::init(argc, argv); auto node = rclcpp::Node::make_shared("talker");创建 publisher 和 rate 对象的方式基本一致,只是命名空间和方法名有所不同。
// 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。
详情请参阅
设计文档
和 概念概述。
创建消息时的命名空间有所不同:
// std_msgs::String msg; std_msgs::msg::String msg;用 rclcpp::ok() 代替 ros::ok():
// while (ros::ok()) while (rclcpp::ok())在发布循环中,data 字段的访问方式不变:
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());将 publish 调用中的 . 运算符改为 ->。
// chatter_pub.publish(msg); chatter_pub->publish(msg);Spin(即让通信系统处理所有待处理的传入/传出消息,直到没有更多工作为止)也有所不同:该调用现在接受节点和超时作为参数:
// ros::spinOnce(); rclcpp::spin_all(node, 0s);rate 对象的 sleep 调用保持不变。
修改完成后的 talker.cpp 如下所示:
#include <chrono>#include <sstream>// #include "ros/ros.h"#include "rclcpp/rclcpp.hpp"// #include "std_msgs/String.h"#include "std_msgs/msg/string.hpp"
using namespace std::chrono_literals;
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_all(node, 0s); loop_rate.sleep(); } return 0;}更改 package.xml
Section titled “更改 package.xml”ROS 2 软件包使用来自 ament_cmake_ros(而非 catkin)的 CMake 函数和宏。
首先删除对 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 代码
Section titled “更改 CMake 代码”ROS 2 要求使用更新版本的 CMake,才能使 ament_cmake 函数正常工作。
cmake_minimum_required(VERSION 3.20)根据 REP 2000 中目标 ROS 发行版所要求的版本,使用相应的 C++ 标准。
如果使用 C++20,则在 project(talker) 调用之后添加以下代码片段来设置标准。
同时建议加上编译器检查,这是一个好习惯。
if(NOT CMAKE_CXX_STANDARD) set(CMAKE_CXX_STANDARD 20)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 targets。
target_link_libraries(talker PUBLIC rclcpp::rclcpp std_msgs::std_msgs)删除 include_directories() 调用,在 add_executable(talker talker.cpp) 下方添加 target_include_directories() 调用。
注意不要将 rclcpp_INCLUDE_DIRS 等变量传入 target_include_directories()。
通过使用现代 CMake targets 调用 target_link_libraries(),include 目录已经自动处理了。
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.20)project(talker)if(NOT CMAKE_CXX_STANDARD) set(CMAKE_CXX_STANDARD 20)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::std_msgs)install(TARGETS talker DESTINATION lib/${PROJECT_NAME})ament_package()构建 ROS 2 代码
Section titled “构建 ROS 2 代码”先 source 环境设置文件(本例中使用的是按照 ROS 2 安装教程生成的那一个,构建在 ~/ros2_ws 中),然后使用 colcon build 构建软件包:
. ~/ros2_ws/install/setup.bashcd ~/ros2_talkercolcon build运行 ROS 2 节点
Section titled “运行 ROS 2 节点”由于 talker 可执行文件已安装到正确的目录中,在 source 安装树的设置文件后,可以通过以下命令运行它:
. ~/ros2_ws/install/setup.bashros2 run talker talker至此,你已经学会了如何将一个 C++ ROS 1 示例软件包迁移到 ROS 2。 在迁移自己的 C++ 软件包时,可参考 C++ 软件包迁移参考页面。