ROS2笔记
读鱼香ROS后记,以上内容全部出自鱼香ROS,仅个人记录学习。
用g++编译ROS2的C++节点
在 first_ros2_node.cpp
中输入下面的代码。
1 2 3 4 5 6 7 8 9 10 11 12
| // 包含rclcpp头文件,如果Vscode显示红色的波浪线也没关系 // 我们只是把VsCode当记事本而已,谁会在意记事本对代码的看法呢,不是吗? #include "rclcpp/rclcpp.hpp"
int main(int argc, char **argv) { // 调用rclcpp的初始化函数 rclcpp::init(argc, argv); // 调用rclcpp的循环运行我们创建的first_node节点 rclcpp::spin(std::make_shared<rclcpp::Node>("first_node")); return 0; }
|
编译
接着我们使用g++来编译 first_ros2_node
节点。正常的话一定会报错。
报错内容如下:
1 2 3 4 5
| root@490925f19143:~/d2lros2/d2lros2/chapt2/basic# g++ first_ros2_node.cpp first_ros2_node.cpp:3:10: fatal error: rclcpp/rclcpp.hpp: No such file or directory 3 | #include "rclcpp/rclcpp.hpp" | ^~~~~~~~~~~~~~~~~~~ compilation terminated.
|
一定要记住这个错误 No such file or directory
,这将是你接下来机器人学习工作生涯中最常见的错误之一。
最终解决一层一层的头文件导入后因该是这样的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| g++ first_ros2_node.cpp \ -I/opt/ros/jazzy/include/rclcpp/ \ -I /opt/ros/jazzy/include/rcl/ \ -I /opt/ros/jazzy/include/rcutils/ \ -I /opt/ros/jazzy/include/rmw \ -I /opt/ros/jazzy/include/rcl_yaml_param_parser/ \ -I /opt/ros/jazzy/include/rosidl_runtime_c \ -I /opt/ros/jazzy/include/rosidl_typesupport_interface \ -I /opt/ros/jazzy/include/rcpputils \ -I /opt/ros/jazzy/include/builtin_interfaces \ -I /opt/ros/jazzy/include/rosidl_runtime_cpp \ -I /opt/ros/jazzy/include/tracetools \ -I /opt/ros/jazzy/include/rcl_interfaces \ -I /opt/ros/jazzy/include/libstatistics_collector \ -I /opt/ros/jazzy/include/statistics_msgs \ -I /opt/ros/jazzy/include/service_msgs \ -I /opt/ros/jazzy/include/type_description_interfaces \ -I /opt/ros/jazzy/include/rosidl_dynamic_typesupport \ -I /opt/ros/jazzy/include/rosidl_typesupport_introspection_cpp \
|
运行完上面这段代码,你会发现报的错误变了。
1 2 3 4 5 6 7 8 9 10 11 12
| /usr/bin/ld: /tmp/ccoA8hho.o: in function `main': first_ros2_node.cpp:(.text+0x37): undefined reference to `rcutils_get_default_allocator' /usr/bin/ld: first_ros2_node.cpp:(.text+0x5c): undefined reference to `rclcpp::InitOptions::InitOptions(rcutils_allocator_s)' /usr/bin/ld: first_ros2_node.cpp:(.text+0x7d): undefined reference to `rclcpp::init(int, char const* const*, rclcpp::InitOptions const&, rclcpp::SignalHandlerOptions)' /usr/bin/ld: first_ros2_node.cpp:(.text+0x89): undefined reference to `rclcpp::InitOptions::~InitOptions()' /usr/bin/ld: first_ros2_node.cpp:(.text+0xb1): undefined reference to `rclcpp::spin(std::shared_ptr<rclcpp::Node>)' /usr/bin/ld: first_ros2_node.cpp:(.text+0xe9): undefined reference to `rclcpp::InitOptions::~InitOptions()' /usr/bin/ld: /tmp/ccoA8hho.o: in function `void __gnu_cxx::new_allocator<rclcpp::Node>::construct<rclcpp::Node, char const (&) [11]>(rclcpp::Node*, char const (&) [11])': first_ros2_node.cpp:(.text._ZN9__gnu_cxx13new_allocatorIN6rclcpp4NodeEE9constructIS2_JRA11_KcEEEvPT_DpOT0_[_ZN9__gnu_cxx13new_allocatorIN6rclcpp4NodeEE9constructIS2_JRA11_KcEEEvPT_DpOT0_]+0x86): undefined reference to `rcutils_get_default_allocator' /usr/bin/ld: first_ros2_node.cpp:(.text._ZN9__gnu_cxx13new_allocatorIN6rclcpp4NodeEE9constructIS2_JRA11_KcEEEvPT_DpOT0_[_ZN9__gnu_cxx13new_allocatorIN6rclcpp4NodeEE9constructIS2_JRA11_KcEEEvPT_DpOT0_]+0xb7): undefined reference to `rclcpp::NodeOptions::NodeOptions(rcutils_allocator_s)' /usr/bin/ld: first_ros2_node.cpp:(.text._ZN9__gnu_cxx13new_allocatorIN6rclcpp4NodeEE9constructIS2_JRA11_KcEEEvPT_DpOT0_[_ZN9__gnu_cxx13new_allocatorIN6rclcpp4NodeEE9constructIS2_JRA11_KcEEEvPT_DpOT0_]+0xe7): undefined reference to `rclcpp::Node::Node(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, rclcpp::NodeOptions const&)' collect2: error: ld returned 1 exit status
|
原因在于g++找不到库文件,解决方法就是我们帮助它定位到库文件的位置,并通过-L参数指定库目录,-l(小写L)指定库的名字。
1
| -L /opt/ros/jazzy/lib/ \
|
得到可执行程序 first_node
,运行 ./first_node
即可执行。
使用make编译ROS2节点
安装make
编写Makefile
在first_ros2_node.cpp同目录下新建 Makefile
,然后将上面的g++编译指令用下面的形式写到Makefile里。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| build: g++ first_ros2_node.cpp \ -I/opt/ros/jazzy/include/rclcpp/ \ -I /opt/ros/jazzy/include/rcl/ \ -I /opt/ros/jazzy/include/rcutils/ \ -I /opt/ros/jazzy/include/rmw \ -I /opt/ros/jazzy/include/rcl_yaml_param_parser/ \ -I /opt/ros/jazzy/include/rosidl_runtime_c \ -I /opt/ros/jazzy/include/rosidl_typesupport_interface \ -I /opt/ros/jazzy/include/rcpputils \ -I /opt/ros/jazzy/include/builtin_interfaces \ -I /opt/ros/jazzy/include/rosidl_runtime_cpp \ -I /opt/ros/jazzy/include/tracetools \ -I /opt/ros/jazzy/include/rcl_interfaces \ -I /opt/ros/jazzy/include/libstatistics_collector \ -I /opt/ros/jazzy/include/statistics_msgs \ -I /opt/ros/jazzy/include/service_msgs \ -I /opt/ros/jazzy/include/type_description_interfaces \ -I /opt/ros/jazzy/include/rosidl_dynamic_typesupport \ -I /opt/ros/jazzy/include/rosidl_typesupport_introspection_cpp \ -L /opt/ros/jazzy/lib/ \ -lrclcpp -lrcutils \ -o first_node # 顺便小鱼加个clean指令,用来删掉first_node clean: rm first_node
|
编译
在Makefile同级目录输入
make指令调用了脚本里的build下的指令,对代码进行了编译。同级目录下也产生了first_node可执行文件(绿色代表可执行)。
使用 make clean
指令即可删掉 first_node
节点。
运行测试
新开终端
使用CMakeLists.txt编译ROS2节点
虽然通过make调用Makefile编译代码非常的方便,但是还是需要我们手写gcc指令来编译,那有没有什么办法可以自动生成Makefile呢?
答案是有的,那就是cmake工具。
cmake通过调用CMakeLists.txt直接生成Makefile。
安装Cmake
新建CMakeLists.txt
在 d2lros2/d2lros2/chapt2/basic
新建 CMakeLists.txt
,输入下面内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| cmake_minimum_required(VERSION 3.22)
project(first_node)
#include_directories 添加特定的头文件搜索路径 ,相当于指定g++编译器的-I参数 include_directories(/opt/ros/humble/include/rclcpp/) include_directories(/opt/ros/humble/include/rcl/) include_directories(/opt/ros/humble/include/rcutils/) include_directories(/opt/ros/humble/include/rcl_yaml_param_parser/) include_directories(/opt/ros/humble/include/rosidl_runtime_c/) include_directories(/opt/ros/humble/include/rosidl_typesupport_interface/) include_directories(/opt/ros/humble/include/rcpputils/) include_directories(/opt/ros/humble/include/builtin_interfaces/) include_directories(/opt/ros/humble/include/rmw/) include_directories(/opt/ros/humble/include/rosidl_runtime_cpp/) include_directories(/opt/ros/humble/include/tracetools/) include_directories(/opt/ros/humble/include/rcl_interfaces/) include_directories(/opt/ros/humble/include/libstatistics_collector/) include_directories(/opt/ros/humble/include/statistics_msgs/)
# link_directories - 向工程添加多个特定的库文件搜索路径,相当于指定g++编译器的-L参数 link_directories(/opt/ros/humble/lib/)
# add_executable - 生成first_node可执行文件 add_executable(first_node first_ros2_node.cpp)
# target_link_libraries - 为first_node(目标) 添加需要动态链接库,相同于指定g++编译器-l参数 # 下面的语句代替 -lrclcpp -lrcutils target_link_libraries(first_node rclcpp rcutils)
|
编译代码
我们一般会创建一个新的目录,运行cmake并进行编译,这样的好处是不会显得那么乱。
创建好文件夹,接着运行cmake指令,..
代表到上级目录找 CMakeLists.txt
。
运行完cmake你应该可以在build目录下看到cmake自动生成的Makefile了,接着就可以运行make指令进行编译
运行完上面的指令,就可以在build目录下发现 first_node
节点了。
CMake依赖查找流程
上面我们用g++、make、cmake三种方式来编译ros2的C++节点。用cmake虽然成功了,但是CMakeLists.txt的内容依然非常的臃肿,我们需要将其进一步的简化。
优化CMakeList.txt
将上面的CmakLists.txt改成下面的样子
1 2 3 4 5 6
| cmake_minimum_required(VERSION 3.22) project(first_node)
find_package(rclcpp REQUIRED) add_executable(first_node first_ros2_node.cpp) target_link_libraries(first_node rclcpp::rclcpp)
|
接着继续生成和编译
find_package查找路径
find_package查找路径对应的环境变量如下。
1 2 3 4 5
| <package>_DIR CMAKE_PREFIX_PATH CMAKE_FRAMEWORK_PATH CMAKE_APPBUNDLE_PATH PATH
|
打开终端,输入指令:
结果
1
| PATH=/opt/ros/humble/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
观察PATH变量,你会发现 /opt/ros/humble/bin
赫然在其中,PATH
中的路径如果以 bin
或 sbin
结尾,则自动回退到上一级目录,接着检查这些目录下的
1 2 3
| <prefix>/(lib/<arch>|lib|share)/cmake/<name>*/ (U) <prefix>/(lib/<arch>|lib|share)/<name>*/ (U) <prefix>/(lib/<arch>|lib|share)/<name>*/(cmake|CMake)/ (U)
|
cmake找到这些目录后,会开始依次找 <package>Config.cmake
或 Find<package>.cmake
文件。找到后即可执行该文件并生成相关链接信息。
打开 /opt/ros/humble/share/rclcpp/cmake
你会发现 rclcppConfig.cmake
就在其中。
编写ROS2的Python节点
在 d2lros2/d2lros2/chapt2/basic
新建 second_ros2_node.py
,输入下面的内容
1 2 3 4 5 6 7 8
| # 导入rclpy库,如果Vscode显示红色的波浪线也没关系 # 我们只是把VsCode当记事本而已,谁会在意记事本对代码的看法呢,不是吗? import rclpy from rclpy.node import Node # 调用rclcpp的初始化函数 rclpy.init() # 调用rclcpp的循环运行我们创建的second_node节点 rclpy.spin(Node("second_node"))
|
运行Python节点
打开终端,输入指令
1 2
| ls python3 second_ros2_node.py
|
打开新的终端,输入
完美,四行代码写了个ROS2的Python节点。
那么问题来了,我们import rclpy,rclpy到底在哪里?python是如何找到的?
Python包查找流程
Python3运行 import rclpy
时候如何找到它的呢?答案是通过环境变量 PYTHONPATH
Ctrl+C打断节点运行,接着输入下面指令
结果
1
| /opt/ros/humble/lib/python3.10/site-packages:/opt/ros/humble/local/lib/python3.10/dist-packages
|
你会发现里面有关于humble的python路径,在上面两个目录下找一下rclpy,看看能不能找到rclpy
查找第一个路径
1
| ls -l /opt/ros/humble/lib/python3.10/site-packages | grep rclpy
|
没找到,第二个
1
| ls -l /opt/ros/humble/local/lib/python3.10/dist-packages/ | grep rclpy
|
找到了
1 2
| drwxr-xr-x 1 root root 4096 Jun 3 04:45 rclpy drwxr-xr-x 2 root root 4096 May 23 22:23 rclpy-3.3.4-py3.10.egg-info
|
删除路径实验
使用 export
指令可以重新修改环境变量的值,尝试修改掉 PYTHONPATH
中ROS 2 相关内容后之后再运行代码,看看是否还可以导入 rclpy
。
1 2 3
| export PYTHONPATH=/opt/ros/humble/lib/python3.10/site-packages echo $PYTHONPATH #重新echo查看 python3 second_ros2_node.py
|
提示如下
1 2 3 4 5
| root@490925f19143:~/d2lros2/d2lros2/chapt2/basic# python3 second_ros2_node.py Traceback (most recent call last): File "/root/d2lros2/d2lros2/chapt2/basic/second_ros2_node.py", line 3, in <module> import rclpy ModuleNotFoundError: No module named 'rclpy'
|
请你记住这个报错信息 ModuleNotFoundError: No module named 'xxx'
,这也是你未来学习过程中可能会经常会遇到的。
下次遇到时,你会想起小鱼这篇文章,然后对它轻蔑的一笑,接着找到这个库所在的目录,把它加到环境里。
ROS节点
ROS2中每一个节点也是只负责一个单独的模块化的功能(比如一个节点负责控制车轮转动,一个节点负责从激光雷达获取数据、一个节点负责处理激光雷达的数据、一个节点负责定位等等)
上面举了一个激光雷达的例子,一个节点负责获取激光雷达的扫描数据,一个节点负责处理激光雷达数据,比如去除噪点。
那节点与节点之间就必须要通信了,那他们之间该如何通信呢?ROS2早已为你准备好了一共四种通信方式:
- 话题-topics
- 服务-services
- 动作-Action
- 参数-parameters
如何启动一个节点?
知道了节点的概念之后,我们该如何启动一个节点呢?
因为工作空间和包的概念,小鱼放到了下一讲,这里大家跟着小鱼一起运行一个节点,感受一下。
使用指令:
1
| ros2 run <package_name> <executable_name>
|
指令意义:启动 包下的 中的节点。
使用样例:
1
| ros2 run turtlesim turtlesim_node
|
大家可以尝试一下上面的指令,就是我们在第一章中启动小乌龟模拟器的那条指令。
运行之后可以看到一只小乌龟,接下来就可以试试下一节中提到的几个指令来查看节点信息和列表。
通过命令行界面查看节点信息
ROS2的CLI,就是和ROS2相关的命令行操作。什么是命令行界面呢?这里小鱼再讲解一个概念,CLI(Command-Line Interface)和GUI(Graphical User Interface)
- GUI(Graphical User Interface)就是平常我们说的图形用户界面,大家用的Windows是就是可视化的,我们可以通过鼠标点击按钮等图形化交互完成任务。
- CLI(Command-Line Interface)就是命令行界面了,我们所用的终端,黑框框就是命令行界面,没有图形化。
很久之前电脑还是没有图形化界面的,所有的交互都是通过命令行实现,就学习机器人而言,命令行操作相对于图形化优势更加明显。
ROS2为我们提供了一系列指令,通过这些指令,可以实现对ROS2相关模块信息的获取设置等操作。
运行节点(常用)
1
| ros2 run <package_name> <executable_name>
|
查看节点列表(常用):
查看节点信息(常用):
1
| ros2 node info <node_name>
|
重映射节点名称
1
| ros2 run turtlesim turtlesim_node --ros-args --remap __node:=my_turtle
|
运行节点时设置参数
1
| ros2 run example_parameters_rclcpp parameters_basic --ros-args -p rcl_log_level:=10
|