将 ROS 1 包转换为 ROS 2包例子
前言
假设我们有一个简单的名为 ROS 1 的包,它 在一个节点talker中使用, 此软件包位于 catkin 工作区中,位于.roscpptalker~/ros1_talker
一、ROS1代码
1. ros1工作空区的目录布局
$ cd ~/ros1_talker
$ find .
.
./src
./src/talker
./src/talker/package.xml
./src/talker/CMakeLists.txt
./src/talker/talker.cpp
2. 代码
2.1 src/talker/package.xml
<package>
<name>talker</name>
<version>0.0.0</version>
<description>talker</description>
<maintainer email="aamir.com">Aamir</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>
2.2 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 src/talker.cpp)
target_link_libraries(talker ${catkin_LIBRARIES})
install(TARGETS talker
RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION})
2.3 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;
}
3. 构建 ROS 1 代码
我们获取一个环境设置文件(在本例中为 noetic 使用 bash),然后我们使用以下命令构建我们的包:catkin_make install(也可以按照自己习惯编译,需要使用ros1的环境)
. /opt/ros/noetic/setup.bash
cd ~/ros1_talker
catkin_make install
4. 运行 ROS 1 节点
4.1 运行主节点
如果还没有运行,我们启动一个roscore,首先从我们的catkin安装树中获取安装文件(系统安装文件 /opt/ros/noetic/setup.bash):
. ~/ros1_talker/install/setup.bash
roscore
4.2 运行tlaker节点
在另一个 shell 中,我们使用 运行catkin安装空间中 的节点rosrun,再次首先获取安装文件(在这种情况下,它必须是我们工作区中的那个):
. ~/ros1_talker/install/setup.bash
rosrun talker talker
二、迁移到 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. 两个库之间的概念非常相似,这使得更改相当简单。
1. 修改C++文件
1. 1修改头文件
使用rclcpp/rclcpp.hpp代替ros/ros.h,它使我们能够访问roscpp库 API,它使我们能够访问rclcpp 库 API:
//#include "ros/ros.h"
#include "rclcpp/rclcpp.hpp"
要获得std_msgs/String消息定义, 需要将std_msgs/String.h替换为std_msgs/msg/string.hpp:
//#include "std_msgs/String.h"
#include "std_msgs/msg/string.hpp"
1.2. 更改 C++ 库调用
我们没有将节点名称传递给库初始化调用,而是进行初始化,然后将节点名称传递给节点对象的创建(我们可以使用auto关键字,因为现在我们需要 C++14 编译器):
// 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);
发消息的创建在命名空间中有所不同:
// std_msgs::String msg;
std_msgs::msg::String msg;
将rclcpp::ok()替代ros::ok():
// while (ros::ok())
while (rclcpp::ok())
在发布循环中,我们data像以前一样访问该字段:
msg.data = ss.str();
要打印控制台消息,我们使用 RCLCPP_INFO()及其各种表式,而不是使用ROS_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);
spin(即让通信系统处理任何待处理的传入/传出消息)的不同之处在于调用现在将节点作为参数:
// ros::spinOnce();
rclcpp::spin_some(node);
1.3 C++完整代码
#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;
}
2 修改package.xml
ROS 2 不支持包规范的格式 1,但仅支持较新的格式版本(2 和更高版本)。我们首先在package标签中指定格式版本:
<!-- <package> -->
<package format="2">
ROS 2 使用更新版本的catkin,称为ament_cmake,我们在 buildtool_depend标签中指定:
<!-- <buildtool_depend>catkin</buildtool_depend> -->
<buildtool_depend>ament_cmake</buildtool_depend>
在我们的构建依赖项中,roscpp我们使用rclcpp提供了我们使用的 C++ API 的而不是我们。
<!-- <build_depend>roscpp</build_depend> -->
<build_depend>rclcpp</build_depend>
我们在运行依赖项中进行了相同的添加,并从 run_depend标记更新到exec_depend标记(升级到包格式版本 2 的一部分):
<!-- <run_depend>roscpp</run_depend> -->
<exec_depend>rclcpp</exec_depend>
<!-- <run_depend>std_msgs</run_depend> -->
<exec_depend>std_msgs</exec_depend>
在 ROS 1 中,我们使用来简化指定编译时和运行时的依赖关系。我们可以在 ROS 2 中做同样的事情:
<depend>rclcpp</depend>
<depend>std_msgs</depend>
我们还需要告诉构建工具我们是什么类型的包,以便它知道如何构建我们。因为我们使用的是amentCMake,所以我们添加以下行来声明我们的构建类型为ament_cmake:
<export>
<build_type>ament_cmake</build_type>
</export>
2.1 package.xml完整代码
<!-- <package> -->
<package format="2">
<name>talker</name>
<version>0.0.0</version>
<description>talker</description>
<maintainer email="aamir.com">aamir</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>
3 修改CMake 代码
ROS 2 依赖于3.5或更高版本的 CMake:
#cmake_minimum_required(VERSION 2.8.3)
cmake_minimum_required(VERSION 3.5)
ROS 2 依赖于 C++14 标准。根据您使用的编译器,默认情况下可能不会启用对 C++14 的支持。使用gcc5.3(这是在 Ubuntu Xenial 上使用的),我们需要显式启用它,在所有平台上工作的首选方式是,我们通过在文件顶部附近添加以下行来实现:
if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 14)
endif()
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
用,我们通过在最初发现自己时catkin将它们作为参数传递来指定我们想要构建的包。使用,我们分别找到每个包,从 开始:COMPONENTScatkinament_cmakeament_cmake
#find_package(catkin REQUIRED COMPONENTS roscpp std_msgs)
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
可以像以前一样找到系统依赖项:
find_package(Boost REQUIRED COMPONENTS system filesystem thread)
我们调用catkin_package()为使用我们包的其他包自动生成诸如 CMake 配置文件之类的东西。虽然该调用发生在指定要构建的目标之前,但我们现在在目标ament_package() 之后调用类似的调用:
# catkin_package()
# At the bottom of the file:
ament_package()
唯一需要手动包含的目录是本地目录和不是ment包的依赖项:
#include_directories(${catkin_INCLUDE_DIRS})
include_directories(include ${Boost_INCLUDE_DIRS})
更好的选择是单独为每个目标指定包含目录,而不是包含所有目标的所有目录:
target_include_directories(target include ${Boost_INCLUDE_DIRS})
与我们分别找到每个依赖包的方式类似,我们需要将每个依赖包链接到构建目标。与作为ment 包的依赖包链接,而不是使用 target_link_libraries(),ament_target_dependencies()是处理构建标志的更简洁和更彻底的方法。它会自动处理定义的包含目录和定义的 _INCLUDE_DIRS链接库_LIBRARIES。
#target_link_libraries(talker ${catkin_LIBRARIES})
ament_target_dependencies(talker
rclcpp
std_msgs)
要链接不是修改包的包,例如系统依赖项Boost,或构建在相同的库CMakeLists.txt,请使用 target_link_libraries():
target_link_libraries(target ${Boost_LIBRARIES})
对于安装,catkin定义变量,如CATKIN_PACKAGE_BIN_DESTINATION. 使用ament_cmake,我们只给出一个相对于安装根目录的路径,例如bin 可执行文件:
#install(TARGETS talker
# RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION})
install(TARGETS talker
DESTINATION lib/${PROJECT_NAME})
3.1 CMakeLists.txt完整代码
#cmake_minimum_required(VERSION 2.8.3)
cmake_minimum_required(VERSION 3.5)
project(talker)
if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 14)
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()
4 编译和运行ROS2代码
4.1 编译
我们获取一个环境设置文件(在本例中是通过遵循 ROS 2 安装教程生成的,它内置于~/ros2_ws,然后我们使用以下方式构建我们的包:colcon build
. ~/ros2_ws/install/setup.bash
cd ~/ros2_talker
colcon build
4.2 运行 ROS 2 节点
因为我们将talker可执行文件安装到bin中,在获取安装文件后,从我们的安装树中,我们可以直接按名称调用它(此外,还没有 ROS 2 等效项rosrun):
. ~/ros2_ws/install/setup.bash
talker
注:如果这个编译不行,那么就按照自己习惯的方式就行。进入ros2环境,运行:
ros2 run talker talker