前言
笔者发现现在市面上(主要是某站上)的ros2相关的教程内容大多不太基础(例如古某居(手动狗头保命),对于那些不熟悉Linux和C++甚至Python的童鞋来说不太友好,之前笔者自己在跟着学习过程中也有不少疑惑,踩了不少坑,现进行一些前置知识的总结(主要学习古某居和鱼香ros的教程,墙裂推荐),一方面对自己的学习做一些记录,另一方面也希望能对别的童鞋入门有帮助。当然,这并不是一个完整的ROS2教程,只是笔者根据自身学习过程中的所遇到的疑惑和问题情况进行的一些总结。
环境变量
我们在终端中执行ros2 run package_name node_name 来启动节点时,其实是通过$AMENT_PREFIX_PATH这个环境变量来找到路径的,开始时这个环境变量中只存了opt/ros/humble,我们创建一个新的功能包并编译后,通过
source install/setup.bash
执行脚本(另外提一句,install目录下有好几个setup脚本,不同终端对应不同的脚本,.sh是一种通用性的)来改变这个环境变量,实际上就是将这个功能包的路径添加到$AMENT_PREFIX_PATH这个环境变量中;另外如果是Python功能包会将install的lib目录下Python包路径添加到$PYTHONPATH环境变量中。需要注意的是这些更改只对当前终端生效,打开一个新的终端时直接ros2 run 启动这个节点的话就会报错找不到这个功能包。在古月居的教程中,则是直接将这样一句source的指令添加到了.bashrc中,每次打开一个新的终端时会自动调用,省去自己手动调用的麻烦。(在安装ros2时也会自动把
source opt/ros/humble/setup.bash
指令保存到.bashrc中,每次打开新终端时自动调用,更新ros2相关的环境变量)
通过printenv打印出所有的环境变量,可以通过| grep过滤出想要查看的环境变量。export可以更改环境变量,但也只对当前终端生效。
另外我们在终端中很多类似的操作都是同样通过环境变量来实现的,比如在终端中直接python3运行某个Python文件,是因为$PATH中保存了Python的bin文件的路径,可以直接调用该python3解释器。可以通过
which python3
whereis python3
查看相关信息。
VSCode中python代码提示也是通过环境变量实现的,$PYTHONPATH环境变量中存了python库的路径,在代码中import导入某个库后,调用库中的函数时,编辑器进行搜索
python基础
库导入
通过import module会导入整个模块,然后通过module.a调用该模块中的函数、类等,这样不会导致命名重复的问题;import module as ...可以改名字,简写模块名称,一般有某些固定简写名称,比如import numpy as np;from ... import ...则是导入特定的函数或者类等,一般需要调用模块中特定对象时使用,需要注意可能的命名问题;如果from ... import *则导入所有对象,一般不这样。
程序入口配置
与C++不同的时,Python程序不存在真正的"main"函数,也就是说Python程序执行时不会把自己定义的main函数当作程序入口(程序的主要逻辑还是写在main函数里,其实可以不命名为main,只是一种习惯性的做法),所以我们在ROS2中写Python节点时需要在setup文件中配置节点可执行文件的入口为某个.py文件的main函数(通常我们都在main函数中定义节点对象并spin运行)。另外我们在调试时没有创建功能包,只写了一个.py文件,main函数中定义了节点对象并spin,可以在该文件中加上一下内容:
if __name__=="main":
main()
则可以直接运行该节点,调试时比较方便,省去创建功能包和编译的麻烦。(这两句代码在Python中是为了在脚本被直接运行时执行main()
函数,而在脚本被作为模块导入时不执行)
C++基础
共享指针
ROS2中的节点对象通常使用共享指针来进行管理。创建节点时,通常会使用std::make_shared
来生成共享指针。
// 使用std::shared_ptr创建节点
auto node = std::make_shared<MyNode>();
在ROS2中,定时器(timer)通常与共享指针一起使用,以确保定时器回调函数中的资源在使用时不会被释放。
rclcpp::TimerBase::SharedPtr timer_; // 使用共享指针管理定时器
// 创建一个定时器,使用共享指针
timer_ = this->create_wall_timer(
std::chrono::seconds(1),
std::bind(&TimerNode::timer_callback, this));
在消息传递中,std::shared_ptr
被广泛用于发布或订阅消息时,确保消息的正确生命周期管理。
void publish_message() {
auto message = std::make_shared<std_msgs::msg::String>();
message->data = "Hello, ROS2!";
RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message->data.c_str());
publisher_->publish(*message); // 发布消息
}
Lamda表达式
在ROS2中,订阅者、服务、定时器等需要回调函数来处理事件。通常可以将这些回调函数作为lambda表达式传入。
auto subscription = node->create_subscription<std_msgs::msg::String>(
"topic",
10,
// 使用lambda表达式作为回调函数
[](const std_msgs::msg::String::SharedPtr msg) {
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "I heard: '%s'", msg->data.c_str());
});
auto timer = node->create_wall_timer(
std::chrono::milliseconds(500),
// 使用lambda表达式作为定时器回调
[]() {
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Timer triggered");
});
std::bind
当需要传递一个成员函数作为回调时,直接传递this
指针不够,因为成员函数依赖对象的上下文。通过std::bind
可以将成员函数和对象实例绑定,从而正确传递回调函数。
// 使用std::bind将成员函数与对象实例绑定,并生成可调用对象
subscription_ = this->create_subscription<std_msgs::msg::String>(
"topic", 10,
std::bind(&MyNode::topic_callback, this, std::placeholders::_1));
}
C++功能包构建
在 ROS2 开发中,依赖管理和库的查找主要通过 CMakeLists.txt 和 package.xml 实现,具体步骤如下:
find_package()
查找 ROS2 包。ament_target_dependencies()
将依赖包关联到目标。install()
安装可执行文件。ament_export_dependencies()
导出包的依赖关系。package.xml
声明编译时和运行时依赖。- 通过
colcon
和ament_cmake
构建和管理依赖。
以下是一个典型的CMakeLists.txt文件:
cmake_minimum_required(VERSION 3.5)
project(my_cpp_package)
# 查找依赖库
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
# 添加可执行文件
add_executable(my_cpp_node src/my_cpp_node.cpp)
# 链接依赖库
ament_target_dependencies(my_cpp_node rclcpp std_msgs)
# 安装可执行文件
install(TARGETS
my_cpp_node
DESTINATION lib/${PROJECT_NAME})
# 使package能够被ament构建
ament_package()
标签:std,函数,package,前置,基础知识,main,环境变量,ROS2
From: https://blog.csdn.net/Huizhongs/article/details/137174865