迁移 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。