创建包
# ament_cmake,默认
ros2 pkg create --build-type ament_cmake <package_name>
# cmake
ros2 pkg create --build-type cmake <package_name>
迁移包
depend>foo</depend>
<<!-- <depend>等于以下 -->
build_depend>foo</build_depend>
<build_export_depend>foo</build_export_depend>
<exec_depend>foo</exec_depend>
<<!-- ros2没有以下 -->
run_depend>foo</run_depend> <
迁移接口
消息,服务和动作在ROS统称接口(interfaces)
package依赖添加
buildtool_depend>rosidl_default_generators</buildtool_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>
<depend>message_package</depend> <
CMake添加C++17支持
set(CMAKE_CXX_STANDARD 17)
find_package(rosidl_default_generators REQUIRED)
find_package(message_package REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
"msg/Num.msg"
"msg/Sphere.msg"
"srv/AddThreeInts.srv"
# Add packages that above messages depend on, in this case geometry_msgs for Sphere.msg
DEPENDENCIES geometry_msgs )
自定义消息文档: https://docs.ros.org/en/rolling/Tutorials/Beginner-Client-Libraries/Custom-ROS2-Interfaces.html
迁移C++包
构建工具
使用colcon
,教程: https://docs.ros.org/en/rolling/Tutorials/Beginner-Client-Libraries/Colcon-Tutorial.html
构建系统
ROS2 使用ament
构建
在package.xml
里面加上
buildtool_depend>ament_cmake</buildtool_depend>
<<!-- your dependencies -->
export>
<build_type>ament_cmake</build_type>
<export> </
找包方式
find_package(ament_cmake REQUIRED)
find_package(component1 REQUIRED)
# ...
find_package(componentN REQUIRED)
在所有包注册好后调用ament_package
catkin_package
里面的CATKIN_DEPENDS
、INCLUDE_DIRS
和LIBRARIES
改为使用三条cmake函数ament_export_dependencies
、ament_export_include_directories
和ament_export_libraries
,这几条命令需要在ament_package
之前调用
替换add_message_files
, add_service_files
和
generate_messages
为
rosidl_generate_interfaces
rosidl_generate_interfaces(${PROJECT_NAME}
${msg_files}
DEPENDENCIES std_msgs )
单元测试
检查器(Linters)
更新源码
消息,服务和动作
ROS2的消息,服务和动作都在不同的命名空间下,例如#include <my_interfaces/msg/my_message.hpp>
,my_interfaces::msg::MyMessage
共享指针的类型也变了:my_interfaces::msg::MyMessage::SharedPtr
更详细的文档: generated C++ interfaces
使用服务
ROS2服务没有返回值了
// 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迁移
- 把所有的
ros::Time
改为rclcpp::Time
- 如果代码中有用到
std_msgs::Time
:- 把所有
td_msgs::Time
实例改为builtin_interfaces::msg::Time
- 把所有包含
#include "std_msgs/time.h
改为#include "builtin_interfaces/msg/time.hpp"
- 把所有
std_msgs::Time
中的nsec
改为builtin_interfaces::msg::Time
的nanosec
- 把所有
ros::Time::now()
改为node->now()
namespace rclcpp {
...
explicit Clock(rcl_clock_type_t clock_type = RCL_SYSTEM_TIME);
}
[!NOTE] ROS2中不同时钟类型(
clock_type
)的时间(rclcpp::Time
)类型是不能进行运算比较等操作的。rclcpp::Clock().now()
返回的是系统时间, 加参数RCL_ROS_TIME
后等同ros::Time::now()
,默认是系统时间,也就是WallTime
ROS2中rclcpp::Time
如果没初始化就做比较会崩溃,报错,和上面的问题是一样的,不像ROS1,默认用0给ros::Time
赋值
terminate called after throwing an instance of 'std::runtime_error'
what(): can't subtract times with different time sources [2 != 1]
用以下代码检查报文是否初始化时间戳
auto locateTime = rclcpp::Time(locate.header.stamp);
if (locateTime.get_clock_type() == RCL_CLOCK_UNINITIALIZED) {
(0.5, "定位时间戳类型错误");
LOGW_HZ}
ros::Rate迁移
把ros::Rate
改为rclcpp::Rate
Boost
不受影响,注意版本接口就行,但是ROS2自带的函数和智能指针全用std
标准库的了
线程和锁
boost::mutex::scoped_lock
->std::unique_lock<std::mutex>
boost::mutex
->std::mutex
#include <boost/thread/mutex.hpp>
->#include <mutex>
无序map
boost改为标准c++的
function
boost改为标准c++的
实际迁移例子
迁移Python包
略
迁移launch文件
设计文档: https://docs.ros.org/en/rolling/Tutorials/Intermediate/Launch/Launch-system.html
例子: http://design.ros2.org/articles/roslaunch_xml.html
迁移标签
launch
ROS2中根元素为launch
node
type
->exec
ns
->namespace
- 移除
machine
,respawn_delay
,clear_params
.
param
- ROS2没有全局参数,不支持的标签有
type
,textfile
,binfile
,executable
command
属性变为value="$(command '...' )"
类型规则
例子
node pkg="my_package" exec="my_executable" name="my_node">
<<!--A string parameter with value "1"-->
param name="a_string" value="'1'"/>
<<!--A integer parameter with value 1-->
param name="an_int" value="1"/>
<<!--A float parameter with value 1.0-->
param name="a_float" value="1.0"/>
<<!--A string parameter with value "asd"-->
param name="another_string" value="asd"/>
<<!--Another string parameter, with value "asd"-->
param name="string_with_same_value_as_above" value="'asd'"/>
<<!--Another string parameter, with value "'asd'"-->
param name="quoted_string" value="\'asd\'"/>
<<!--A list of strings, with value ["asd", "bsd", "csd"]-->
param name="list_of_strings" value="asd, bsd, csd" value-sep=", "/>
<<!--A list of ints, with value [1, 2, 3]-->
param name="list_of_ints" value="1,2,3" value-sep=","/>
<<!--Another list of strings, with value ["1", "2", "3"]-->
param name="another_list_of_strings" value="'1';'2';'3'" value-sep=";"/>
<<!--A list of strings using an strange separator, with value ["1", "2", "3"]-->
param name="strange_separator" value="'1'//'2'//'3'" value-sep="//"/>
<node> </
参数组
node pkg="my_package" exec="my_executable" name="my_node" ns="/an_absoulute_ns">
<param name="group1">
<param name="group2">
<param name="my_param" value="1"/>
<param>
</param name="another_param" value="2"/>
<param>
</node> </
rosparam
从其他文件中加载配置,改为from
node pkg="my_package" exec="my_executable" name="my_node" ns="/an_absoulute_ns">
<param from="/path/to/file"/>
<node> </
remap
只准在node
中使用
include
arg
value
标签不允许,使用let
标签doc
变为description
- 在
include
标签里面时,if
,unless
和description
都不允许
tf到tf2静态变换迁移
node pkg="tf2" exec="static_transform_publisher" name="vehicleTransPublisher"
< args="-$(arg sensorOffsetX) -$(arg sensorOffsetY) 0 0 0 0 /sensor /vehicle 1000"/>
node pkg="tf2_ros" exec="static_transform_publisher" name="vehicleTransPublisher" args="-$(var sensorOffsetX) -$(var sensorOffsetY) 0 0 0 0 /sensor /vehicle"/> <
迁移参数
略
迁移脚本
自动转换工具
启动文件迁移器,用于将ROS 1的XML启动文件转换为ROS 2的Python启动文件:https://github.com/aws-robotics/ros2-launch-file-migrator
亚马逊已经提供了他们用于从ROS 1迁移到ROS 2的工具,链接如下:https://github.com/awslabs/ros2-migration-tools/tree/master/porting_tools
rospy2 Python项目,用于自动将rospy调用转换为rclpy调用
其他迁移技巧
ROS命令行
rosrun
->ros2 run
rosparam
->ros2 param
- 其他类似
ROS命令行参数
ROS2命令行参数必须在--ros-args
之后
ros2 run some_package some_ros_executable --ros-args -r foo:=bar
参数
ros2 run some_package some_ros_executable --ros-args -p my_param:=value
更改节点名
ros2 run some_package some_ros_executable --ros-args -r __node:=new_node_name
更改命名空间
ros2 run some_package some_ros_executable --ros-args -r __ns:=/new/namespace
ROS发布订阅
#include <rclcpp/rclcpp.hpp>
#include <std_msgs/msg/string.hpp>
#include <sensor_msgs/msg/point_cloud2.hpp>
class MyNode : public rclcpp::Node
{
public:
() : Node("my_node")
MyNode{
publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
subscription_ = this->create_subscription<sensor_msgs::msg::PointCloud2>(
"topic", rclcpp::SensorDataQoS(), [this] (sensor_msgs::msg::PointCloud2::ConstSharedPtr msg) { topic_callback(msg); });
}
private:
void topic_callback(sensor_msgs::msg::PointCloud2::ConstSharedPtr msg) const
{
(this->get_logger(), "I heard: '%s'", msg->data.c_str());
RCLCPP_INFO}
private:
::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
rclcpp}
int main(int argc, char * argv[])
{
::init(argc, argv);
rclcpp::spin(std::make_shared<MyNode>());
rclcpp::shutdown();
rclcppreturn 0;
}
使用lambda表达式可以避免使用std::bind
绑定订阅回调函数时出现以下错误(不一定出现):
113 | const_shared_ptr_callback_ = callback;
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~
In file included from /usr/include/c++/9/future:48,
from /opt/ros/foxy/include/rclcpp/executors.hpp:18,
from /opt/ros/foxy/include/rclcpp/rclcpp.hpp:146,
from /home/jiang/code/general_robot/src/locate_3d/src/LidarLocate.hpp:5,
from /home/jiang/code/general_robot/src/locate_3d/src/LidarLocate.cpp:1:
/usr/include/c++/9/bits/std_function.h:462:7: note: candidate: ‘std::function<_Res(_ArgTypes ...)>& std::function<_Res(_ArgTypes ...)>::operator=(const std::function<_Res(_ArgTypes ...)>&) [with _Res = void; _ArgTypes = {std::shared_ptr<const geometry_msgs::msg::PoseStamped_<std::allocator<void> > >}]’
462 | operator=(const function& __x)
| ^~~~~~~~
/usr/include/c++/9/bits/std_function.h:462:33: note: no known conversion for argument 1 from ‘std::_Bind<void (LidarLocate::*(LidarLocate*, boost::arg<1>))(std::shared_ptr<const geometry_msgs::msg::PoseStamped_<std::allocator<void> > >)>’ to ‘const std::function<void(std::shared_ptr<const geometry_msgs::msg::PoseStamped_<std::allocator<void> > >)>&’
462 | operator=(const function& __x)
| ~~~~~~~~~~~~~~~~^~~
/usr/include/c++/9/bits/std_function.h:480:7: note: candidate: ‘std::function<_Res(_ArgTypes ...)>& std::function<_Res(_ArgTypes ...)>::operator=(std::function<_Res(_ArgTypes ...)>&&) [with _Res = void; _ArgTypes = {std::shared_ptr<const geometry_msgs::msg::PoseStamped_<std::allocator<void> > >}]’
480 | operator=(function&& __x) noexcept
| ^~~~~~~~
/usr/include/c++/9/bits/std_function.h:480:28: note: no known conversion for argument 1 from ‘std::_Bind<void (LidarLocate::*(LidarLocate*, boost::arg<1>))(std::shared_ptr<const geometry_msgs::msg::PoseStamped_<std::allocator<void> > >)>’ to ‘std::function<void(std::shared_ptr<const geometry_msgs::msg::PoseStamped_<std::allocator<void> > >)>&&’
480 | operator=(function&& __x) noexcept
| ~~~~~~~~~~~^~~
/usr/include/c++/9/bits/std_function.h:494:7: note: candidate: ‘std::function<_Res(_ArgTypes ...)>& std::function<_Res(_ArgTypes ...)>::operator=(std::nullptr_t) [with _Res = void; _ArgTypes = {std::shared_ptr<const geometry_msgs::msg::PoseStamped_<std::allocator<void> > >}; std::nullptr_t = std::nullptr_t]’
494 | operator=(nullptr_t) noexcept
| ^~~~~~~~
/usr/include/c++/9/bits/std_function.h:494:17: note: no known conversion for argument 1 from ‘std::_Bind<void (LidarLocate::*(LidarLocate*, boost::arg<1>))(std::shared_ptr<const geometry_msgs::msg::PoseStamped_<std::allocator<void> > >)>’ to ‘std::nullptr_t’
494 | operator=(nullptr_t) noexcept
| ^~~~~~~~~
/usr/include/c++/9/bits/std_function.h:523:2: note: candidate: ‘template<class _Functor> std::function<_Res(_ArgTypes ...)>::_Requires<std::function<_Res(_ArgTypes ...)>::_Callable<typename std::decay<_Functor>::type>, std::function<_Res(_ArgTypes ...)>&> std::function<_Res(_ArgTypes ...)>::operator=(_Functor&&) [with _Functor = _Functor; _Res = void; _ArgTypes = {std::shared_ptr<const geometry_msgs::msg::PoseStamped_<std::allocator<void> > >}]’
523 | operator=(_Functor&& __f)
| ^~~~~~~~
代码的C++标准为17
另外
rclcpp::Subscription
的take
函数:
rclcpp::Subscription
的take
函数用于从订阅的主题中取出一条消息。这个函数会尝试从订阅的主题中取出一条消息,并将其放入提供的消息实例中。如果成功取出一条消息,函数会返回true
,否则返回false
。
这是take
函数的原型:
bool take(MessageT & message_out, rmw_message_info_t & message_info_out);
其中,MessageT
是消息的类型,message_out
是一个消息实例的引用,用于存储取出的消息。message_info_out
是一个rmw_message_info_t
实例的引用,用于存储关于取出的消息的元信息,如发布者的信息。==不确定能不能自己用==
动作的设计文档:http://design.ros2.org/articles/actions.html
动作客户端例子
#include <rclcpp/rclcpp.hpp>
#include <rclcpp_action/rclcpp_action.hpp>
#include <your_action_pkg/action/YourAction.hpp> // 替换为你的动作消息头文件
class ActionClientNode : public rclcpp::Node
{
public:
() : Node("action_client_node")
ActionClientNode{
this->client_ptr_ = rclcpp_action::create_client<your_action_pkg::action::YourAction>(this,
"your_action_name"); // 替换为你的动作名称
// 创建一个目标
auto goal_msg = your_action_pkg::action::YourAction::Goal();
// ... 填充目标消息
// 发送目标,并绑定结果回调函数
this->client_ptr_->async_send_goal(goal_msg, std::bind(&ActionClientNode::goal_response_callback, this, std::placeholders::_1));
}
private:
void goal_response_callback( rclcpp_action::ClientGoalHandle<your_action_pkg::action::YourAction>::SharedPtr goal_handle)
{
if (!goal_handle) {
(this->get_logger(), "Goal was rejected by server");
RCLCPP_ERROR} else {
(this->get_logger(), "Goal accepted by server, waiting for result");
RCLCPP_INFO->async_result(std::bind(&ActionClientNode::result_callback, this, std::placeholders::_1));
goal_handle}
}
void feedback_callback( rclcpp_action::ClientGoalHandle<your_action_pkg::action::YourAction>::SharedPtr goal_handle,
const std::shared_ptr<const your_action_pkg::action::YourAction::Feedback> feedback)
{
}
void result_callback(const rclcpp_action::ClientGoalHandle<your_action_pkg::action::YourAction>::WrappedResult & result)
{
switch(result.code) {
case rclcpp_action::ResultCode::SUCCEEDED:
// 处理成功的结果
break;
case rclcpp_action::ResultCode::ABORTED:
// 处理被服务器中止的结果
break;
case rclcpp_action::ResultCode::CANCELED:
// 处理被客户端取消的结果
break;
default:
// 处理其他结果
break;
}
}
::Client<your_action_pkg::action::YourAction>::SharedPtr client_ptr_;
rclcpp_action};
int main(int argc, char **argv)
{
::init(argc, argv);
rclcppauto node = std::make_shared<ActionClientNode>();
::spin(node);
rclcpp::shutdown();
rclcppreturn 0;
}
动作服务端例子
#include <rclcpp/rclcpp.hpp>
#include <rclcpp_action/rclcpp_action.hpp>
#include <your_action_pkg/action/YourAction.hpp> // 替换为你的动作消息头文件
class ActionServerNode : public rclcpp::Node
{
public:
() : Node("action_server_node")
ActionServerNode{
using GoalHandle = rclcpp_action::ServerGoalHandle<your_action_pkg::action::YourAction>;
this->server_ptr_ = rclcpp_action::create_server<your_action_pkg::action::YourAction>(this,
"your_action_name", // 替换为你的动作名称
std::bind(&ActionServerNode::handle_goal, this, std::placeholders::_1, std::placeholders::_2),
std::bind(&ActionServerNode::handle_cancel, this, std::placeholders::_1),
std::bind(&ActionServerNode::handle_accepted, this, std::placeholders::_1));
}
private:
::GoalResponse handle_goal(
rclcpp_actionconst rclcpp_action::GoalUUID & uuid,
::action::YourAction::Goal::ConstSharedPtr goal)
your_action_pkg{
// 处理新的目标请求
return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
}
::CancelResponse handle_cancel(
rclcpp_actionstd::shared_ptr<rclcpp_action::ServerGoalHandle<your_action_pkg::action::YourAction>> goal_handle)
{
// 处理取消请求
return rclcpp_action::CancelResponse::ACCEPT;
}
void handle_accepted( std::shared_ptr<rclcpp_action::ServerGoalHandle<your_action_pkg::action::YourAction>> goal_handle)
{
// 处理已接受的目标
}
::Server<your_action_pkg::action::YourAction>::SharedPtr server_ptr_;
rclcpp_action};
int main(int argc, char **argv)
{
::init(argc, argv);
rclcppauto node = std::make_shared<ActionServerNode>();
::spin(node);
rclcpp::shutdown();
rclcppreturn 0;
}
服务客户端例子
#include <rclcpp/rclcpp.hpp>
#include <your_srv_pkg/srv/YourService.hpp> // 替换为你的服务消息头文件
class ServiceClientNode : public rclcpp::Node
{
public:
() : Node("service_client_node")
ServiceClientNode{
this->client_ptr_ = this->create_client<your_srv_pkg::srv::YourService>("your_service_name"); // 替换为你的服务名称
// 创建一个服务请求
auto request = std::make_shared<your_srv_pkg::srv::YourService::Request>();
// ... 填充请求消息
// 发送服务请求,并绑定响应回调函数
this->client_ptr_->async_send_request(request, std::bind(&ServiceClientNode::response_callback, this, std::placeholders::_1));
}
private:
void response_callback(const typename your_srv_pkg::srv::YourService::Response::SharedPtr response)
{
// 处理服务响应
}
::Client<your_srv_pkg::srv::YourService>::SharedPtr client_ptr_;
rclcpp};
int main(int argc, char **argv)
{
::init(argc, argv);
rclcppauto node = std::make_shared<ServiceClientNode>();
::spin(node);
rclcpp::shutdown();
rclcppreturn 0;
}
服务服务端例子
#include <rclcpp/rclcpp.hpp>
#include <your_srv_pkg/srv/YourService.hpp> // 替换为你的服务消息头文件
class ServiceServerNode : public rclcpp::Node
{
public:
() : Node("service_server_node")
ServiceServerNode{
this->server_ptr_ = this->create_service<your_srv_pkg::srv::YourService>(
"your_service_name", // 替换为你的服务名称
std::bind(&ServiceServerNode::handle_service, this, std::placeholders::_1, std::placeholders::_2));
}
private:
void handle_service(
const your_srv_pkg::srv::YourService::Request::SharedPtr request,
::srv::YourService::Response::SharedPtr response)
your_srv_pkg{
// 处理服务请求,并填充响应消息
}
::Service<your_srv_pkg::srv::YourService>::SharedPtr server_ptr_;
rclcpp};
int main(int argc, char **argv)
{
::init(argc, argv);
rclcppauto node = std::make_shared<ServiceServerNode>();
::spin(node);
rclcpp::shutdown();
rclcppreturn 0;
}
函数替换:
getNumSubscribers()
->get_subscription_count()
跳过包编译
ROS1中在包根目录touch CATKIN_IGNORE
,ROS2则是touch COLCON_IGNORE
消息修改
命名规则
- 消息字段为
xxxx_xxxx
命名风格 - 消息常量为
XXXX_XXXX
命名风格
常用消息迁移
ROS1类型 | ROS2类型 | 匹配正则表达式 |
---|---|---|
Header |
std_msgs/Header |
^\s*Header |
time |
builtin_interfaces/Time |
^\s*time |
单独编译包
单独编译某个包并把cmake输出打印到命令行
colcon build --event-handlers console_direct+ --packages-select foo_package
CMake打印所有当前环境变量
get_cmake_property(_variableNames VARIABLES)
foreach (_variableName ${_variableNames})
message(STATUS "${_variableName}=${${_variableName}}")
endforeach()
PCL
ROS2中没有pcl_ros
包了,需要
find_package(pcl_conversions REQUIRED)
find_package(PCL REQUIRED COMPONENTS common io)
link_directories(${PCL_LIBRARY_DIRS})
add_definitions(${PCL_DEFINITIONS})
老的tf的一些函数实现
#include <geometry_msgs/msg/quaternion.hpp>
#include <tf2_geometry_msgs/tf2_geometry_msgs.h>
#include <tf2/LinearMath/Quaternion.h>
namespace tf
{
inline geometry_msgs::msg::Quaternion createQuaternionMsgFromYaw(double yaw)
{
::Quaternion q;
tf2.setRPY(0, 0, yaw);
qreturn tf2::toMsg(q);
}
inline geometry_msgs::msg::Quaternion createQuaternionMsgFromRollPitchYaw(double roll, double pitch, double yaw)
{
::Quaternion q;
tf2.setRPY(roll, pitch, yaw);
qreturn tf2::toMsg(q);
}
inline tf2::Quaternion createQuaternionFromYaw(double yaw)
{
::Quaternion q;
tf2.setRPY(0, 0, yaw);
qreturn q;
}
inline tf2::Quaternion createQuaternionFromRPY(double roll, double pitch, double yaw)
{
::Quaternion q;
tf2.setRPY(roll, pitch, yaw);
qreturn q;
}
}
tf2没有tf::Pose
,改用tf2::Transform
在<tf2_geometry_msgs/tf2_geometry_msgs.h>
中包含消息和tf2数据转换
tf2::fromMsg()
和tf2::toMsg()
geometry_msgs::msg::Pose
<=>tf2::Transform
geometry_msgs::msg::Quaternion
<=>tf2::Quaternion
geometry_msgs::msg::TransformStamped
<=>tf2::Stamped<tf2::Transform>
另外还有把消息里面的timestamp
转tf2的tf2::TimePoint
,tf2_ros::fromMsg
tf2::getYaw
和tf2::getEulerYPR
在<tf2/utils.h>
里面
spdlog冲突
如果使用自己编译封装的spdlog库打印日志,rclcpp::init
需要添加参数才不会冲突
::InitOptions options;
rclcpp.auto_initialize_logging(false);
options::init(argc, argv, options); rclcpp
ROS2CLion
https://www.jetbrains.com/help/clion/ros2-tutorial.html
ROS2适配CLion的顶层CMakeLists.txt
用这种方法不需要参考CLion官方的导入compile_commands.json
的办法
把该CMakeLists.txt
放到工作目录下,和src
平级
cmake_minimum_required(VERSION 3.14)
project("ROS2 Master")
# usually I put this in a separate file include("/opt/ros/_common/Colcon.cmake")
function(colcon_add_subdirectories)
cmake_parse_arguments(PARSE_ARGV 0 "ARG" "" "BUILD_BASE;BASE_PATHS" "")
message("search criteria: ${ARGV}")
execute_process(COMMAND colcon list
--paths-only${ARG_BASE_PATHS}
--base-paths
--topological-order${ARG_UNPARSED_ARGUMENTS}
OUTPUT_VARIABLE paths)
string(STRIP "${paths}" paths)
string(REPLACE "\n" ";" paths "${paths}"
)
MESSAGE("colcon shows paths ${paths}")
foreach(path IN LISTS paths)
message("...examining ${path}")
# if(EXISTS "${path}/CMakeLists.txt")
execute_process(COMMAND colcon info --paths "${path}" OUTPUT_VARIABLE package_info)
if(NOT "${package_info}" MATCHES "type:[ \t]+(cmake|ros.ament_cmake|ros.cmake)")
message("skipping non-cmake project")
elseif(NOT "${package_info}" MATCHES "name:[ \t]+([^ \r\n\t]*)")
message(WARNING "could not identify package at ${path}")
else()
set(name "${CMAKE_MATCH_1}")
message("...adding package ${name} from path ${path}")
MESSAGE("package info: ${package_info}")
get_filename_component(BUILD_PATH "${name}" ABSOLUTE BASE_DIR "${ARG_BUILD_BASE}")
add_subdirectory("${path}" "${BUILD_PATH}")
endif()
endforeach()
endfunction()
colcon_add_subdirectories(
"${PROJECT_SOURCE_DIR}/build"
BUILD_BASE "${PROJECT_SOURCE_DIR}/src/"
BASE_PATHS # 指定单独编译包
# --packages-select locate_node
)
先编译一遍工程文件,然后source install/setup.bash
,然后用命令行启动CLion打开工程选该CMakeLists.txt
即可
使用该脚本会碰到自己写的包相互依赖出问题,因为脚本中使用的是add_subdirectory("${path}" "${BUILD_PATH}")
,假设两个自己写的包package_a和package_b,如果pacakge_b依赖package_a,如果CMakeLists中出现以下语句会出问题:
find_package(package_a REQUIRED)
# 上面语句可以改为
find_package(package_a QUIET)
ament_target_dependencies(${PROJECT_NAME} package_a)
上面语句需要在顶层CMake中定义一个变量 USE_AMENT 开关ament,把因为QUIET导入包出问题的ament语句放进去
if (USE_AMENT)
ament_target_dependencies(${PROJECT_NAME} package_a)
endif ()
不建议用该配置,修改后还是会出各种问题,因为不能用ament_target_dependencies
了,和用纯CMake写工程差不多
ROS2
colcon
跟ROS1的构建工具catkin_make
区别在于前者是每个包单独CMake编译,后者是所有包一起CMake编译,catkin build
没试过
原脚本地址: https://gist.github.com/rotu/1eac858b808b82bbf1b475f515e91636
tf到tf2
tf::Pose
没了,需要用tf2::transform
替代
geometry_msgs/PoseStamped
和tf2::transform
的相互转换
// 创建一个PoseStamped消息
::msg::PoseStamped pose_stamped_msg;
geometry_msgs
// 转换为tf2::Transform
::Transform tf2_transform;
tf2::fromMsg(pose_stamped_msg.pose, tf2_transform);
tf2
// 创建一个tf2::Transform
::Transform tf2_transform_new;
tf2
// 转换为PoseStamped消息
::msg::PoseStamped pose_stamped_msg_new;
geometry_msgs.pose = tf2::toMsg(tf2_transform_new); pose_stamped_msg_new
包里面链接同一包的消息
if(${rosidl_cmake_VERSION} VERSION_LESS 2.5.0)
rosidl_target_interfaces(trajectory_analyzer_node
"rosidl_typesupport_cpp")
planning_debug_tools else()
rosidl_get_typesupport_target(
"rosidl_typesupport_cpp")
cpp_typesupport_target planning_debug_tools target_link_libraries(trajectory_analyzer_node "${cpp_typesupport_target}")
endif()
这里的trajectory_analyzer_node
是节点名,planning_debug_tools
是工程名
CMake IN_LIST报错
错误示例:
CMake Error at /opt/ros/humble/share/ament_cmake_core/cmake/core/ament_execute_extensions.cmake:41 (if):
if given arguments:
"ament_cmake_core" "IN_LIST" "_ARG_EXCLUDE"
Unknown arguments specified
Call Stack (most recent call first):
/opt/ros/humble/share/ament_cmake_core/cmake/core/ament_package.cmake:66 (ament_execute_extensions)
CMakeLists.txt:60 (ament_package)
在CMakeLists.txt
的project(xxx)
后面加上如下
if(POLICY CMP0057)
cmake_policy(SET CMP0057 NEW)
endif()
c++ param
[!NOTE] ROS2的param需要先声明后获取,而且launch文件中如果是
double
类型的值一定要用5.0
而非5
这种整型形式
例如
launch>
<node pkg="test_param" exec="test_param">
<param name="a" value="0.0" />
<node>
</launch> </
int main(int argc, char **argv)
{
::init(argc, argv);
rclcpp::Node node("test_param");
rclcppdouble a = 1.0;
// node.declare_parameter("a", rclcpp::ParameterType::PARAMETER_DOUBLE);
// 这种更方便
.declare_parameter("a", a);
node.get_parameter("a", a);
node(node.get_logger(), "a: %lf", a);
RCLCPP_INFO}
.declare_parameter("value_name", default_value);
nodebool result = node.get_parameter("value_name", value);
如果不先调用declare_parameter
则后面的get_parameter
取不到数据
[!NOTE] 一个参数声明一次就够了,多个地方使用的时候不能重复调用
declare_parameter
,不然程序会异常退出
ROS bag
ros1的bag包转ros2的bag包
https://gitlab.com/ternaris/rosbags
文档: https://ternaris.gitlab.io/rosbags/
pip install rosbags
# 然后把`~/.local/bin`加到PATH
rosbags-convert foo.bag
rosbags-convert foo.bag --dst /dir/path/to/save
# 然后在foo目录下能找到.db3的bag包还有metadata.yaml文件
默认存储为db3的sqlite格式,可以用zstd
进行进一步压缩
# 例如
zstd 2023-11-15-14-31-41.db3
# output:
# 2023-11-15-14-31-41.db3 : 54.75% (3678203904 => 2013660420 bytes, 2023-11-15-14-31-41.db3.zst)
压缩后剩下55%左右,如果指定高等级可以压缩更多
# 指定level(1-19)等级为10
zstd -10 2023-11-15-14-31-41.db3
压缩后剩下45%左右
[!TIP]
ros2 bag record
参数里面也可设置zstd压缩
命令行别名
# build package
alias colcon-bp='colcon build --symlink-install --event-handlers console_direct+ --cmake-args -Wno-dev -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -G Ninja --packages-select'
# build all
alias colcon-ba='colcon build --symlink-install --event-handlers console_direct+ --cmake-args -Wno-dev -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -G Ninja'
把该两条命令放在.bashrc
或.zshrc
里面即可使这两条用命令编译包
正则表达式
使用Python脚本替换一些常用的类型和文件
#!/usr/bin/python3
# 该脚本用来初步替换代码中的内容为ROS2的写法
import re
import sys
# 创建一个列表,其中包含所有查找和替换的正则表达式对
= [
cpp_regex r'\b(\w*_msgs)\:\:(?!msg)(\w{1,})\b', r'\1::msg::\2'),
(r'\bconst (\w*_msgs\:\:msg\:\:\w*?)(::)?ConstPtr\s*&\s*(\w*)', r'\1::ConstSharedPtr \3'),
(r'(\w*\-\>header\.stamp)\.toSec\(\)', r'rclcpp::Time(\1).seconds()'),
(r'ros\:\:Time\(\)\.fromSec\((laserCloudTime)\)', r'rclcpp::Time(\1).seconds()'),
(r'ros\:\:Time ', r'rclcpp::Time '),
(r'ROS_(\w+)\(((?:.|\n)*?)\);', r'RCLCPP_\1(node->get_logger(), \2);'),
('<time.h>', '<ctime>'),
('<math.h>', '<cmath>'),
('<stdio.h>', '<cstdio>'),
('<stdlib.h>', '<cstdlib>'),
('<ros/ros.h>', '<rclcpp/rclcpp.hpp>'),
('<std_msgs/Bool.h>', '<std_msgs/msg/bool.hpp>'),
('<std_msgs/Int32.h>', '<std_msgs/msg/int32.hpp>'),
('<std_msgs/UInt32.h>', '<std_msgs/msg/uint32.hpp>'),
('<std_msgs/String.h>', '<std_msgs/msg/string.hpp>'),
('<std_msgs/Int32MultiArray.h>', '<std_msgs/msg/int32_multi_array.hpp>'),
('<std_msgs/Float32MultiArray.h>', '<std_msgs/msg/float32_multi_array.hpp>'),
('<std_msgs/ColorRGBA.h>', '<std_msgs/msg/color_rgba.hpp>'),
('<nav_msgs/Path.h>', '<nav_msgs/msg/path.hpp>'),
('<nav_msgs/Odometry.h>', '<nav_msgs/msg/odometry.hpp>'),
('<geometry_msgs/Pose.h>', '<geometry_msgs/msg/pose.hpp>'),
('<geometry_msgs/Point.h>', '<geometry_msgs/msg/point.hpp>'),
('<geometry_msgs/Polygon.h>', '<geometry_msgs/msg/polygon.hpp>'),
('<geometry_msgs/PoseStamped.h>', '<geometry_msgs/msg/pose_stamped.hpp>'),
('<geometry_msgs/PointStamped.h>', '<geometry_msgs/msg/point_stamped.hpp>'),
('<geometry_msgs/PolygonStamped.h>', '<geometry_msgs/msg/polygon_stamped.hpp>'),
('<sensor_msgs/PointCloud2.h>', '<sensor_msgs/msg/point_cloud2.hpp>'),
('<sensor_msgs/Joy.h>', '<sensor_msgs/msg/joy.hpp>'),
('<visualization_msgs/Marker.h>', '<visualization_msgs/msg/marker.hpp>'),
('<tf/transform_datatypes.h>', '<tf2/transform_datatypes.h>'),
('<tf/transform_broadcaster.h>', '<tf2_ros/transform_broadcaster.h>'),
('tf::TransformBroadcaster', 'tf2_ros::TransformBroadcaster'),
('tf::Matrix3x3', 'tf2::Matrix3x3'),
('ros::Publisher ', 'rclcpp::Publisher<>::SharedPtr '),
('ros::Subscriber ', 'rclcpp::Subscription<>::SharedPtr '),
('tf::Vector3', 'tf2::Vector3'),
('tf::Quaternion', 'tf2::Quaternion'),
('ros::NodeHandle nh;', 'rclcpp::Node node();'),
('ros::Rate', 'rclcpp::Rate'),
('ros::ok', 'rclcpp::ok'),
('ros::init', 'rclcpp::init'),
('ros::spin', 'rclcpp::spin'),
('ros::Timer ', 'rclcpp::TimerBase::SharedPtr '),
('tf2::StampedTransform', 'geometry_msgs::msg::TransformStamped'),
('\.publish', '->publish'),
('boost::bind', 'std::bind'),
(
]
= [
cmake_regex r'cmake_minimum_required\(VERSION (.*)\)', r'cmake_minimum_required(VERSION 3.5)'),
(
]
= [
launch_regex # type换成exec
r'(\<node.*?)type=(.*/?\>)', r'\1exec=\2'),
(# 去掉node里面的required
r'(\<node.*?) required\=\"\w*?\"(.*/?\>)', r'\1\2'),
(# 去掉param里面的type
r'(\<param.*?) type=\"\w*?\"(.*/?\>)', r'\1\2'),
(r'\$\(find ', '$(find-pkg-share '),
(r'\bns\b', 'namespace'),
(r'\$\(arg', '$(var'),
('pkg\=\"rviz\"', 'pkg=\"rviz2\"'),
('name\=\"rviz\"', 'name=\"rviz2\"'),
('exec\=\"rviz\"', 'exec=\"rviz2\"'),
(
]
if __name__ == '__main__':
# 如果argc < 2, 则运行测试代码,否则读取argv[1]文件替换文件内容
= len(sys.argv) > 1
open_file
if open_file:
= open(sys.argv[1]).read()
text else:
= "const sensor_msgs::PointCloud2ConstPtr& laserCloud2"
text
= []
regex_pairs # 根据文件后缀选择正则表达式
if open_file and (sys.argv[1].endswith('.cpp') or sys.argv[1].endswith('.h') or sys.argv[1].endswith('.hpp')):
print('cpp file')
= cpp_regex
regex_pairs elif open_file and (sys.argv[1].endswith('.cmake') or sys.argv[1].endswith('CMakeLists.txt')):
print('cmake file')
= cmake_regex
regex_pairs elif open_file and sys.argv[1].endswith('.launch'):
print('launch file')
= launch_regex
regex_pairs
for find, replace in regex_pairs:
= re.sub(find, replace, text)
text
# 保存文件
if open_file:
with open(sys.argv[1], 'w') as f:
f.write(text)else:
print(text)