节点
ROS:节点是什么
- 机器人是各种功能的综合体,每一项功能就像机器人的一个工作细胞,众多细胞通过一些机制连接到一起,成为了一个机器人整体。
- 在ROS中,我们给这些 “细胞”取了一个名字,那就是节点。
-
- 在ROS中,最小的进程单元就是节点
- 一个软件包里可以有多个可执行文件,可执行文件在运行之后就形成了一股进程,这个进程在ROS中就叫做节点
- 从程序的角度来说,node就是一个可执行文件被执行,加载到了内存中
- 从功能的角度来说,每一个节点也是只负责一个单独的模块化的功能
- 由于机器人的功能模块非常复杂,我们往往不会把所有功能都集中到一个node上,而会采用分布式的方式,把鸡蛋放到不同的篮子里
- (比如一个节点负责控制车轮转动,一个节点负责从激光雷达获取数据、一个节点负责处理激光雷达的数据、一个节点负责定位等等)
完整的机器人系统可能并不是一个物理上的整体,比如这样一个的机器人:
在机器人身体里
- 搭载了一台计算机A,它可以通过机器人的眼睛——摄像头,获取外界环境的信息,也可以控制机器人的腿——轮子,让机器人移动到想要去的地方。
- 可能还会有另外一台计算机B,放在你的桌子上,它可以远程监控机器人看到的信息,也可以远程配置机器人的速度和某些参数,还可以连接一个摇杆,人为控制机器人前后左右运动。
这些功能虽然位于不同的计算机中,但都是这款机器人的工作细胞,也就是节点,他们共同组成了一个完整的机器人系统。
- 节点在机器人系统中的职责就是执行具体的任务,从计算机操作系统的角度来看,也叫做进程;
- 每个节点都是一个可以独立运行的可执行文件,比如执行某一个python程序,或者执行C++编译生成的结果,都算是运行了一个节点;
- 既然每个节点都是独立的执行文件,那自然就可以想到,得到这个执行文件的编程语言可以是不同的,比如C++、Python,乃至Java、Ruby等更多语言。
- 这些节点是功能各不相同的细胞,根据系统设计的不同,可能位于计算机A,也可能位于计算机B,还有可能运行在云端,这叫做分布式,也就是可以分布在不同的硬件载体上;
- 每一个节点都需要有唯一的命名,当我们想要去找到某一个节点的时候,或者想要查询某一个节点的状态时,可以通过节点的名称来做查询。
节点也可以比喻是一个一个的工人,分别完成不同的任务,他们有的在一线厂房工作,有的在后勤部门提供保障,他们互相可能并不认识,但却一起推动机器人这座“工厂”,完成更为复杂的任务。
ROS:节点之间如何交互?
节点和节点之间必须通信,一共有四种通信方式:
- 话题-topics
- 服务-services
- 动作-Action
- 参数-parameters
ROS1:Master
在ROS2中没有master,在ROS1中才有。
ROS1中的master是什么?
- 由于机器人的元器件很多,功能庞大,因此实际运行时往往会运行众多的node,负责感知世界、控制运动、决策和计算等功能。
- 那么如何合理的进行调配、管理这些node?这就要利用ROS提供给我们的节点管理器master, master在整个网络通信架构中相当于管理中心,管理着各个node。
- node首先在master处注册,然后master就会将该node纳入整个ROS程序中
- node之间通信也是先由master进行“牵线”,才能两两的进行点对点通信。
- 当ROS程序启动时,第一步首先启动master,由节点管理器处理依次启动node。
Master、Node之间以及Node之间的关系如下图所示:
ROS2为啥要去掉ROS1
ROS1:启动master和node
当我们要启动ROS时,首先输入命令:
$ roscore
此时ROS master启动,同时启动的还有rosout和parameter server,其中rosout时负责日志输出的一个节点,其作用时告知用户当前系统的状态,包括输出系统的error、warning等,并将log记录于日志文件中,paraameter server即参数服务器,它并不是一个node,而实存储参数配置的一个服务器。
每一次我们运行ROS的节点之前,必须先启动master,这样才能让节点启动和注册。
master之后,节点管理器就开始按照系统的安排协调进行启动具体的节点。节点就是一个进程,只不过在ROS中它被赋予了专用的名字里——node。
我们知道一个package中存放着可执行文件,可执行文件是静态的,当系统执行这些可执行文件,将这些文件加载到内存中,它就成为了动态的node。具体启动node的语句是:
$ rosrun pkg_name node_name
通常我们运行ROS,就是按照这样的顺序启动,有时候节点太多,我们会选择用launch文件来启动。
rosrun命令的详细用法如下:
$ rosrun [--prefix cmd] [--debug] pkg_name node_name [ARGS]
rosrun将会寻找PACKAGE下的名为EXECUTABLE的可执行程序,将可选参数ARGS传入。 例如在GDB下运行ros程序:
$ rosrun --prefix 'gdb -ex run --args' pkg_name node_name
rosnode命令的详细作用列表如下:
rosnode命令 | 作用 |
---|---|
rosnode list | 列出当前运行的node信息 |
rosnode info node_name | 显示出node的详细信息 |
rosnode kill node_name | 结束某个node |
rosnode ping | 测试连接节点 |
rosnode machine | 列出在特定机器或列表机器上运行的节点 |
rosnode cleanup | 清除不可到达节点的注册信息 |
rosnode help |
ROS2:如何启动一个节点?
使用指令:
ros2 run <package_name> <executable_name>
指令意义:启动 包下的 中的节点。
使用样例:
ros2 run turtlesim turtlesim_node
ROS2:通过命令行界面查看节点信息
运行节点(常用)
ros2 run <package_name> <executable_name>
查看节点列表(常用):
ros2 node list
查看节点信息(常用):
ros2 node info <node_name>
重映射节点名称
ros2 run turtlesim turtlesim_node --ros-args --remap __node:=my_turtle
运行节点时设置参数
ros2 run example_parameters_rclcpp parameters_basic --ros-args -p rcl_log_level:=10
我们知道,运行一个节点的命令是:
ros2 run 包名字 可执行文件名字
那么,如果我们想要找到一个节点(可执行文件),就必须先知道它在哪个包。那么,想要找到某个包,应该去哪里找呢?
答案是工作空间。
注意,一个工作空间下可以有多个功能包,一个功能包可以有多个节点存在
工作空间
作空间是包含若干个功能包的目录,一开始大家把工作空间理解成一个文件夹就行了。这个文件夹包含下有src。所以一般新建一个工作空间的操作就像下面一样
cd d2lros2/chapt2/
mkdir -p chapt2_ws/src
操作
用g++编译ROS2的C++节点
编写节点
打开终端,创建chapt2/basic目录,用VSCODE打开d2lros2目录。
mkdir -p d2lros2/chapt2/basic/
code d2lros2
接着在左侧chapt2上新建first_ros2_node.cpp,然后在first_node.cpp中输入下面的代码。
// 包含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_node节点。正常的话一定会报错
g++ first_ros2_node.cpp
内容如下:
fatal error: rclcpp/rclcpp.hpp: 没有那个文件或目录
3 | #include "rclcpp/rclcpp.hpp"
| ^~~~~~~~~~~~~~~~~~~
compilation terminated.
原因:我们在代码里包了"rclcpp/rclcpp.hpp"头文件,但是g++找不到这个头文件,解决方法就是告诉g++这个头文件的目录。
解决:
- 首先我们要找到这个头文件在哪里,这个头文件是ROS2的客户端库,其地址肯定在ROS2的安装目录下,即/opt/ros/humble/include/rclcpp。
cd /opt/ros/humble/include/rclcpp
ls rclcpp/* | grep rclcpp.h
重新编译:
g++ first_ros2_node.cpp -I /opt/ros/humble/include/rclcpp/
出现错误:
In file included from /opt/ros/humble/include/rclcpp/rclcpp/executors/multi_threaded_executor.hpp:25,
from /opt/ros/humble/include/rclcpp/rclcpp/executors.hpp:21,
from /opt/ros/humble/include/rclcpp/rclcpp/rclcpp.hpp:155,
from first_ros2_node.cpp:3:
/opt/ros/humble/include/rclcpp/rclcpp/executor.hpp:30:10: fatal error: rcl/guard_condition.h: 没有那个文件或目录
30 | #include "rcl/guard_condition.h"
| ^~~~~~~~~~~~~~~~~~~~~~~
compilation terminated.
还是错误,最终的所有头结点:
g++ first_ros2_node.cpp \
-I/opt/ros/humble/include/rclcpp/ \
-I /opt/ros/humble/include/rcl/ \
-I /opt/ros/humble/include/rcutils/ \
-I /opt/ros/humble/include/rmw \
-I /opt/ros/humble/include/rcl_yaml_param_parser/ \
-I /opt/ros/humble/include/rosidl_runtime_c \
-I /opt/ros/humble/include/rosidl_typesupport_interface \
-I /opt/ros/humble/include/rcpputils \
-I /opt/ros/humble/include/builtin_interfaces \
-I /opt/ros/humble/include/rosidl_runtime_cpp \
-I /opt/ros/humble/include/tracetools \
-I /opt/ros/humble/include/rcl_interfaces \
-I /opt/ros/humble/include/libstatistics_collector \
-I /opt/ros/humble/include/statistics_msgs
出现错误undefined reference to xxxxx:
/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)指定库的名字。
ROS2相关的库的地址都在/opt/ros/humble/lib下,你可以使用下面的指定看到rclcpp的动态链接库。
ls /opt/ros/humble/lib | grep rclcpp
指定库目录和使用的库后的终极命令:
g++ first_ros2_node.cpp \
-I/opt/ros/humble/include/rclcpp/ \
-I /opt/ros/humble/include/rcl/ \
-I /opt/ros/humble/include/rcutils/ \
-I /opt/ros/humble/include/rmw \
-I /opt/ros/humble/include/rcl_yaml_param_parser/ \
-I /opt/ros/humble/include/rosidl_runtime_c \
-I /opt/ros/humble/include/rosidl_typesupport_interface \
-I /opt/ros/humble/include/rcpputils \
-I /opt/ros/humble/include/builtin_interfaces \
-I /opt/ros/humble/include/rosidl_runtime_cpp \
-I /opt/ros/humble/include/tracetools \
-I /opt/ros/humble/include/rcl_interfaces \
-I /opt/ros/humble/include/libstatistics_collector \
-I /opt/ros/humble/include/statistics_msgs \
-L /opt/ros/humble/lib/ \
-lrclcpp -lrcutils
运行后,你会发现没有任何报错了,但是在当前目录下多出了一个a.out,这个就是我们将上面的代码编译和链接完库之后得出的可执行文件。
如果你觉得a.out不好听,可以在g++指定后添加 -o 名字 ,比如 -o first_node
运行节点
./a.out
打开新的终端,使用ros2 node list查看正在运行的节点,是否有first_node。
使用make编译ROS2节点
有没有觉得用g++编译节点无比的麻烦,的确是这样子,为此先行者们发明了一个叫做make的批处理工具,我们可以将g++的指令写成脚本,就可以通过make自动的调用脚本完成操作。
安装make
sudo apt install make
编写Makefile
在d2lros2/d2lros2/chapt2/basic下新建Makefile,然后将上面的g++编译指令用下面的形式写到Makefile里。
build:
g++ first_ros2_node.cpp \
-I/opt/ros/humble/include/rclcpp/ \
-I /opt/ros/humble/include/rcl/ \
-I /opt/ros/humble/include/rcutils/ \
-I /opt/ros/humble/include/rmw \
-I /opt/ros/humble/include/rcl_yaml_param_parser/ \
-I /opt/ros/humble/include/rosidl_runtime_c \
-I /opt/ros/humble/include/rosidl_typesupport_interface \
-I /opt/ros/humble/include/rcpputils \
-I /opt/ros/humble/include/builtin_interfaces \
-I /opt/ros/humble/include/rosidl_runtime_cpp \
-I /opt/ros/humble/include/tracetools \
-I /opt/ros/humble/include/rcl_interfaces \
-I /opt/ros/humble/include/libstatistics_collector \
-I /opt/ros/humble/include/statistics_msgs \
-L /opt/ros/humble/lib/ \
-lrclcpp -lrcutils \
-o first_node
# 顺便小鱼加个clean指令,用来删掉first_node
clean:
rm first_node
使用CMakeLists.txt编译ROS2节点
虽然通过make调用Makefile编译代码非常的方便,但是还是需要我们手写gcc指令来编译,那有没有什么办法可以自动生成Makefile呢?
答案是有的,那就是cmake工具。
cmake通过调用CMakeLists.txt直接生成Makefile。
安装Cmake
sudo apt install cmake
新建CMakeLists.txt
在d2lros2/d2lros2/chapt2/basic新建CMakeLists.txt,输入下面内容。
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)
编译代码
mkdir build
cd build
cmake ..
make
CMake依赖查找流程
上面我们用g++、make、cmake三种方式来编译ros2的C++节点。用cmake虽然成功了,但是CMakeLists.txt的内容依然非常的臃肿,我们需要将其进一步的简化。
优化CMakeList.txt
将上面的CmakLists.txt改成下面的样子
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)
接着继续生成和编译
cmake ..
make
find_package查找路径
find_package查找路径对应的环境变量如下。
<package>_DIR
CMAKE_PREFIX_PATH
CMAKE_FRAMEWORK_PATH
CMAKE_APPBUNDLE_PATH
PATH
打开终端,输入指令:
echo $PATH
结果:
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结尾,则自动回退到上一级目录,接着检查这些目录下的
<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就在其中。
Python依赖查找流程
python的打包和引入依赖的方式相比C++要容易太多。
编写ROS2的Python节点
在d2lros2/d2lros2/chapt2/basic新建second_ros2_node.py,输入下面的内容
# 导入rclpy库,如果Vscode显示红色的波浪线也没关系
# 我们只是把VsCode当记事本而已,谁会在意记事本对代码的看法呢,不是吗?
import rclpy
from rclpy.node import Node
# 调用rclcpp的初始化函数
rclpy.init()
# 调用rclcpp的循环运行我们创建的second_node节点
rclpy.spin(Node("second_node"))
运行Python节点
打开终端,输入指令
python3 second_ros2_node.py
打开新的终端,输入
ros2 node list
完美,四行代码写了个ROS2的Python节点。
那么问题来了,我们import rclpy,rclpy到底在哪里?python是如何找到的?
Python包查找流程
怎么找
Python3运行import rclpy时候如何找到它的呢?答案是通过环境变量PYTHONPATH
Ctrl+C打断节点运行,接着输入下面指令
echo $PYTHONPATH
结果
/opt/ros/humble/lib/python3.10/site-packages:/opt/ros/humble/local/lib/python3.10/dist-packages
你会发现里面有关于humble的python路径,在上面两个目录下找一下rclpy,看看能不能找到rclpy
查找第一个路径
ls -l /opt/ros/humble/lib/python3.10/site-packages | grep rclpy
没找到,第二个
ls -l /opt/ros/humble/local/lib/python3.10/dist-packages/ | grep rclpy
找到了
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
unset实验
使用unset指令可以将环境变量删除掉,尝试删除掉PYTHONPATH之后再运行代码,看看是否还可以导入rclpy。
unset
python3 second_ros2_node.py
提示如下
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’。
怎么解决呢?找到这个库所在的目录,把它加到环境里。
Python打包工具之Setup
本部分只做了解即可,我们平时用的并不多,因为python的依赖并不是靠setup来查找的,但是C++却靠着CmakeLists.txt进行查找。
为什么需要对项目分发打包?
平常我们习惯了使用 pip 来安装一些第三方模块,这个安装过程之所以简单,是因为模块开发者为我们默默地为我们做了所有繁杂的工作,而这个过程就是 打包。
打包,就是将你的源代码进一步封装,并且将所有的项目部署工作都事先安排好,这样使用者拿到后即装即用,不用再操心如何部署的问题(如果你不想对照着一堆部署文档手工操作的话)。
不管你是在工作中,还是业余准备自己写一个可以上传到 PyPI 的项目,你都要学会如何打包你的项目。
Python 发展了这么些年了,项目打包工具也已经很成熟了。他们都有哪些呢?
你可能听过 distutils 、distutils2、setuptools等等,好像很熟悉,却又很陌生,他们都是什么关系呢?
包分发的始祖:distutils
distutils 是 Python 的一个标准库,从命名上很容易看出它是一个分发(distribute)工具(utlis),它是 Python 官方开发的一个分发打包工具,所有后续的打包工具,全部都是基于它进行开发的。
distutils 的精髓在于编写 setup.py,它是模块分发与安装的指导文件。
那么如何编写 setup.py 呢?我会在后面进行详细的解析。
你有可能没写过 setup.py ,但你绝对使用过 setup.py 来做一些事情,比如下面这条命令,我们经常用它来进行模块的安装。
python setup.py install
这样的安装方法是通过源码安装,与之对应的是通过二进制软件包的安装
分发工具升级:setuptools
setuptools 是 distutils 增强版,不包括在标准库中。其扩展了很多功能,能够帮助开发者更好的创建和分发 Python 包。大部分 Python 用户都会使用更先进的 setuptools 模块。
distribute,或许你在其他地方也见过它,这里也提一下。
distribute 是 setuptools 有一个分支版本,分支的原因可能是有一部分开发者认为 setuptools 开发太慢了。但现在,distribute 又合并回了 setuptools 中。因此,我们可以认为它们是同一个东西。
还有一个大包分发工具是 distutils2,其试图尝试充分利用distutils,detuptools 和 distribute 并成为 Python 标准库中的标准工具。但该计划并没有达到预期的目的,且已经是一个废弃的项目。
因此,setuptools 是一个优秀的,可靠的 Python 包安装与分发工具。
超详细讲解 setup.py 的编写
打包分发最关键的一步是编写 setup.py 文件。
以下是一个 setup.py 简单的使用示例
from setuptools import setup, find_packages
setup(
# 指定项目名称,我们在后期打包时,这就是打包的包名称,当然打包时的名称可能还会包含下面的版本号哟~
name="mytest",
# 指定版本号
version="1.0",
author="flp",
author_email="flepeng@163.com",
# 这是对当前项目的一个描述
description="这只是一次测试",
# 项目主页
url="http://iswbm.com/",
# 你要安装的包,通过 setuptools.find_packages 找到当前目录下有哪些包
packages=find_packages()
# 指定包名,即你需要打包的包名称,要实际在你本地存在哟,它会将指定包名下的所有"*.py"文件进行打包哟,但不会递归去拷贝所有的子包内容。
# 综上所述,我们如果想要把一个包的所有"*.py"文件进行打包,应该在packages列表写下所有包的层级关系哟~这样就开源将指定包路径的所有".py"文件进行打包!
packages=['devops', "devops.dev", "devops.ops"],
)
setup 函数常用的参数如下:
功能包
是什么
功能包可以理解为存放节点的地方,ROS2中功能包根据编译方式的不同分为三种类型。
- ament_python,适用于python程序
- cmake,适用于C++
- ament_cmake,适用于C++程序,是cmake的增强版
怎么获取
有两种方式
安装获取
安装一般使用
sudo apt install ros-<version>-package_name
安装获取会自动放置到系统目录,不用再次手动source。
手动编译获取
手动编译相对麻烦一些,需要下载源码然后进行编译生成相关文件。
什么时候需要手动编译呢?
- 一般我们能安装的功能包都是作者编译好程序将可执行文件上传到仓库中,然后我们才能够通过apt进行安装,如果作者还没来得及测试上传,或者忘记了测试上传,就会找不到对应的包,这时候就需要手动编译安装了。
- 另外一种就是我们需要对包的源码进行修改,这个时候也需要自己编译修改。
手动编译之后,需要手动source工作空间的install目录。
与功能包相关的指令 ros2 pkg
create Create a new ROS2 package
executables Output a list of package specific executables
list Output a list of available packages
prefix Output the prefix path of a package
xml Output the XML of the package manifest or a specific tag
创建功能包
ros2 pkg create <package-name> --build-type {cmake,ament_cmake,ament_python} --dependencies <依赖名字>
列出可执行文件
列出所有
ros2 pkg executables
列出turtlesim功能包的所有可执行文件
ros2 pkg executables turtlesim
列出所有的包
ros2 pkg list
输出某个包所在路径的前缀
ros2 pkg prefix <package-name>
比如小乌龟
ros2 pkg prefix turtlesim
列出包的清单描述文件
- 每一个功能包都有一个标配的manifest.xml文件,用于记录这个包的名字,构建工具,编译信息,拥有者,干啥用的等信息。
- 通过这个信息,就可以自动为该功能包安装依赖,构建时确定编译顺序等
查看小乌龟模拟器功能包的信息。
ros2 pkg xml turtlesim
面向过程编写节点
使用RCLCPP编写节点
节点需要存在于功能包当中、功能包需要存在于工作空间当中。所以我们要想创建节点,就要先创建一个工作空间,再创建功能包。
创建工作空间和功能包
(1)创建工作空间
cd d2lros2/chapt2/
mkdir -p chapt2_ws/src/
(2)创建功能包
- 创建example_cpp功能包,使用ament-cmake作为编译类型,并为其添加rclcpp依赖。
- pkg create 是创建包的意思
- –build-type 用来指定该包的编译类型,一共有三个可选项ament_python、ament_cmake、cmake
- –dependencies 指的是这个功能包的依赖,rclcpp表示依赖c++客户端接口
cd chapt2_ws/src
ros2 pkg create example_cpp --build-type ament_cmake --dependencies rclcpp
- 打开终端,进入chapt2_ws/src运行上面的指令,创建完成后的目录结构如下:
.
└── src
└── example_cpp
├── CMakeLists.txt
├── include
│ └── example_cpp
├── package.xml
└── src
5 directories, 2 files
创建节点
接着我们在example_cpp/src下创建一个node_01.cpp文件,创建完成后的目录结构如下:
.
└── src
└── example_cpp
├── CMakeLists.txt
├── include
│ └── example_cpp
├── package.xml
└── src
│ └── node_01.cpp
编写代码
#include "rclcpp/rclcpp.hpp"
int main(int argc, char **argv)
{
/* 初始化rclcpp */
rclcpp::init(argc, argv);
/*产生一个node_01的节点*/
auto node = std::make_shared<rclcpp::Node>("node_01");
// 打印一句自我介绍
RCLCPP_INFO(node->get_logger(), "node_01节点已经启动.");
/* 运行节点,并检测退出信号 Ctrl+C*/
rclcpp::spin(node);
/* 停止运行 */
rclcpp::shutdown();
return 0;
}
修改CmakeLists
在CmakeLists.txt的最后面加入如下:
add_executable(node_01 src/node_01.cpp)
ament_target_dependencies(node_01 rclcpp)
install(TARGETS
node_01
DESTINATION lib/${PROJECT_NAME}
)
编译运行节点
在chapt2_ws下依次输入下面的命令
colcon build
source install/setup.bash
ros2 run example_cpp node_01
当节点运行起来后,可以再尝试使用ros2 node list 指令来查看现有的节点。
使用RCLPY编写节点
创建功能包
cd chapt2/chapt2_ws/src/
ros2 pkg create example_py --build-type ament_python --dependencies rclpy
创建完成后的目录结构
.
├── example_py
│ └── __init__.py
├── package.xml
├── resource
│ └── example_py
├── setup.cfg
├── setup.py
└── test
├── test_copyright.py
├── test_flake8.py
└── test_pep257.py
3 directories, 8 files
编写代码
import rclpy
from rclpy.node import Node
def main(args=None):
"""
ros2运行该节点的入口函数
编写ROS2节点的一般步骤
1. 导入库文件
2. 初始化客户端库
3. 新建节点对象
4. spin循环节点
5. 关闭客户端库
"""
rclpy.init(args=args) # 初始化rclpy
node = Node("node_02") # 新建一个节点
node.get_logger().info("大家好,我是node_02.")
rclpy.spin(node) # 保持节点运行,检测是否收到退出指令(Ctrl+C)
rclpy.shutdown() # 关闭rclpy
修改setup.py
entry_points={
'console_scripts': [
"node_02 = example_py.node_02:main"
],
},
)
setup.py这段配置是声明一个ROS2的节点,声明后使用colcon build才能检测到,从而将其添加到install目录下。
编译运行节点
cd chapt2/chapt2_ws/
colcon build
发现错误:
--- stderr: example_py
/usr/lib/python3/dist-packages/setuptools/command/install.py:34: SetuptoolsDeprecationWarning: setup.py install is deprecated. Use build and pip and other standards-based tools.
warnings.warn(
---
如果在编译中看到上述错误没关系,不影响使用,ros2官方正在修复。 错误原因是setuptools版本太高造成,使用下面的指令可以进行版本的回退。
sudo pip install setuptools==58.2.0 --upgrade
接着source环境并运行节点:
source install/setup.bash
ros2 run example_py node_02
当节点运行起来后,可以再尝试使用ros2 node list 指令来查看现有的节点。
面向对象编写节点
使用RCLCPP编写节点
在d2lros2/chapt2/chapt2_ws/src/example_cpp/src下新建node_03.cpp,接着输入下面的代码。
#include "rclcpp/rclcpp.hpp"
/*
创建一个类节点,名字叫做Node03,继承自Node.
*/
class Node03 : public rclcpp::Node
{
public:
// 构造函数,有一个参数为节点名称
Node03(std::string name) : Node(name)
{
// 打印一句
RCLCPP_INFO(this->get_logger(), "大家好,我是%s.",name.c_str());
}
private:
};
int main(int argc, char **argv)
{
rclcpp::init(argc, argv);
/*产生一个node_03的节点*/
auto node = std::make_shared<Node03>("node_03");
/* 运行节点,并检测退出信号*/
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
接着修改CMakeLists.txt,添加下方代码。
add_executable(node_03 src/node_03.cpp)
ament_target_dependencies(node_03 rclcpp)
install(TARGETS
node_03
DESTINATION lib/${PROJECT_NAME}
)
编译测试:
colcon build --packages-select example_cpp
source install/setup.bash
ros2 run example_cpp node_03
使用RCLPY编写节点
d2lros2/d2lros2/chapt2/chapt2_ws/src/example_py/example_py下新建node_04.py,输入下面的代码
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
class Node04(Node):
"""
创建一个Node04节点,并在初始化时输出一个话
"""
def __init__(self,name):
super().__init__(name)
self.get_logger().info("大家好,我是%s!" % name)
def main(args=None):
rclpy.init(args=args) # 初始化rclpy
node = Node04("node_04") # 新建一个节点
rclpy.spin(node) # 保持节点运行,检测是否收到退出指令(Ctrl+C)
rclpy.shutdown() # 关闭rclpy
接着修改setup.py
entry_points={
'console_scripts': [
"node_02 = example_py.node_02:main",
"node_04 = example_py.node_04:main"
],
},
编译测试
colcon build --packages-select example_py
source install/setup.bash
ros2 run example_py node_04
节点发现
ROS 2用于通讯的默认中间件是DDS。
- 在DDS中,不同逻辑网络共享物理网络的主要机制称为域(Domain) ID。
- 同一域上的ROS 2节点可以自由地相互发现并发送消息,而不同域上的ROS 2节点则不能。
- 所有ROS 2节点默认使用域ID为0。为了避免在同一网络上运行ROS 2的不同计算机组之间互相干扰,应为每组设置不同的域ID。
选择域ID (短版本)
- 选择一个介于0和101之间的安全的域ID (包括0和101)。
选择域ID (长版本)
- 选择一个介于0和232之间的安全的域ID (包括0和232)。
特定平台的约束
为了实现最大的兼容性,在选择域账号时应遵循一些特定于平台的附加约束。特别是,最好避免在操作系统的 临时端口范围 中分配域ID。这避免了ROS 2节点使用的端口与计算机上的其他网络服务之间可能的冲突。
Linux
- 默认情况下,linux内核使用端口32768-60999作为临时端口。这意味着域ID 0-101 和 215-232 可以安全使用,而不会与临时端口发生冲突。
- 临时端口范围可在Linux中通过在 /proc/sys/net/ipv4/ip_local_port_range 中设置自定义值进行配置。如果使用自定义临时端口范围,则可能需要相应地调整上述数字。