ROS2:节点
节点
ROS2中,节点是一个抽象的实体,可以代表某类特定功能的抽象集合体,可以存在于进程或线程中
是ROS2的基础功能的载体,所有通信都需要通过节点来实现
节点和节点执行器
节点执行器executor是协调和调度节点运作的实体,并响应各类通信的回调结果
每个进程有一个或多个节点执行器,每个节点执行器有一个或多个节点
节点执行器有以下几种
- 单线程节点执行器
将所有已添加到维护队列的节点限制在一个线程内处理,占用一个线程资源,并根据其指定的规则对回调顺序和优先级进行设定 - 多线程节点执行器
负责管理的回调函数可以占用多个线程,根据设备性能,动态分配线程数,为队列中的节点处理回调,默认为CPU核心数
std::thread::hardware_concurrency()
获取线程最大数量
线程回调的内容均来自于通信和等待任务,通信包含着订阅、服务、客户端和等待任务 - 静态单线程节点执行器
仅存在于rclcpp中,相对于普通单线程节点执行器可以有更低的CPU和内存占用率
ROS discourse社区的工程师每年在ROS论坛都会讨论新的节点执行器,为了提高其实效率和确定性等指标
建立节点
C++项目中,需要继承rclcpp::Node
基类,Python项目中,需要继承rclcpp.Node
基类
rclcpp默认方式是单线程节点执行器
节点命名规则
- 不能为空
- 第一个字符必须是字母、下划线、波浪号或斜杠
- 后面的字符可以是字母、数字、下划线或斜杠
- 使用下划线开头的节点是隐藏节点
节点执行器不会直接运行,需要通过节点执行器的spin()
函数完成节点的所有回调工作
在rclcpp中提供了多种节点执行器的回调操作模式,常用的spin
,spin_all
和spin_some
等
spin()
该函数会阻塞其他操作,节点执行器会完全并持续地执行回调工作直到被中断
通常在其后面的内容都是结束进程的回收工作spin_all()
该函数不会阻塞其他操作,节点执行器会持续执行所有回调工作,直到超时或无其他工作可做spin_some()
该函数不会阻塞其他操作,节点执行器会完成执行所有在调用spin_some()
时已准备就绪的回调,直到超时或无其他工作可做
与spin_all()
的区别是spin_some()
不会执行在执行过程中产生的回调spin_once()
该函数不会阻塞其他操作,只会执行已准备就绪的第一个回调,直到超时或无其他工作可做spin_node_once()
该函数会令节点执行器临时添加节点,执行一次spin_once()
后,移除节点spin_node_some()
该函数会令节点执行器临时添加节点,执行一次spin_once()
后,移除节点spin_until_future_complete()
该函数会令节点执行器执行回调直到future
的内容完成,或超时,或被中断
调试节点
ros2 node list
ros2 node info
进程、线程与节点的关系
按线程数量分为:单线程节点执行器和多线程节点执行器
使用命令行参数,指定节点数量和使用的执行器
s
表示单线程执行器
m
表示多线程执行器
ros2 run multi_node_cpp multinode 5 s
ros2 run multi_node_cpp multinode 2 m
若不增加节点数量,只增加回调函数和定时器数量,在多线程执行器的维护下,会不会产生更多线程?
创建节点文件
ros2 pkg create test --build-type ament_cmake
--node-name lifecycle --dependencies std_msgs rclcpp
ros2 pkg create node_cpp --node-name node2go
在命令中ros2 pkg create
可选--node-name
参数创建节点文件
或在功能包的src
下手动新建节点文件
发布者节点
修改package.xml
若想发布功能包,需修改package.xml
文件中的description,license标签
在ament_cmake
下边添加
<buildtool_depend>rclcpp</buildtool_depend>
<buildtool_depend>std_msgs</buildtool_depend>
添加依赖
<depend>rclcpp</depend>
修改CMakeLists.txt
find_package(rclcpp)
获取依赖软件包,用于查找当前环境已有的软件包,后期构建使用
会引入一系列变量,如头文件路径和库文件路径等
新增依赖项,用于动态链接和关系导出
ament_target_dependencies
是ament
构建体系中快速动态链接的方法
是ament构建体系中用于快速动态链接的方法,所有通过ament_cmake
构建的软件包都可以通过其进行链接
导出依赖关系
为了其他依赖当前功能包的项目
导出ament依赖项可以使基于ament_cmake
构建的功能包具备递归依赖的能力
添加软件包
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
设置编译规则
add_executable(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp std_msgs)
设置安装规则,这样ros2 run
命令才能找到可执行文件
install(TARGETS
talker
DESTINATION lib/${PROJECT_NAME})
编译运行
确认功能包依赖项
在工作空间dev_ws
下运行rosdep install -i --from-path src --rosdistro eloquent -y
在工作空间目录dev_ws
下使用colcon build
编译
若只想编译指定的包时,可使用colcon build --packages-select <my_package>
在工作空间目录下,使用. install/setup.bash
命令,将工作空间添加到路径
ros2 run my_package my_node
节点常见扩展功能
DDS作为ROS2的网络通信中间件,提供自发现、模块化、低延时和高带宽的通信
还可以通过DDS作用域灵活地将节点的资源隔离,并确保通过不同的网络端口进行通信
除了DDS,命名空间namespace也是一种隔离手段,但其只将节点及其资源的名字通过重映射的方式重命名,并不是实质上将资源隔断
ROS2还提供了可管控生命周期的节点lifecycle nodes,为节点的运行提供了完善的流程,包括节点初始化、配置、激活、失活、问题处理和销毁等阶段,每个阶段均可通过service
操作,或其他形式(如topic
或自行调用)控制
DDS
DDS作为ROS2的网络通信中间件,不仅提供自发现、模块化、低延时和高带宽的通信能力,还提供了消息隔离功能:DDS作用域
通过DDS作用域,可以灵活快速的将节点的资源分隔,并确保通过不同的网络端口进行通信
命名空间也是一直隔离手段,但只是将节点及资源的名字通过重映射的方式重命名,并没有实质上进行隔离
除了传统节点,ROS2还提供了一种可管控生命周期的节点lifecycle nodes
DDS作用域
环境变量ROS_DOMAIN_ID
,该变量继承自DDS体系,域是链接共享相同域的所有应用程序的全局数据空间
域彼此独立,DDS在一个域内发送和接收数据,域ID是一个标识域的正整数
ROS2中,域ID的安全选择范围是[0,101]
,DDS底层是UDP,所以域ID对应的是用于发现和通信的UDP端口,UDP端口是一个无符号16位整数,最大为65535
DDS命名空间
命名规则:
默认命名空间为/
首字符必须为斜杠/
斜杠后的首字符必须为字母或下划线
其他位置可以为字母、数字或下划线
命名空间可以叠加,如/abc/efg/h123
设置命名空间的节点会将其创建的topic,service和action都添加到同一命名空间下,但tf
不会
命名空间可以通过编程或命令行引入,也可以通过launch文件引入
命令行引入
在ros2 run
命令中使用参数--ros-args
后添加--remap __ns:=
指定命名空间
命名空间原理上是通过重映射将原本的节点或其他参数重命名,并未做资源隔离,而域ID则是从网络的维度将资源隔离
不同命名空间的节点可以相互发现和交流,不同域的节点则不行
命名空间原则上数量不受限制,域ID有数量限制
命名空间可以通过初始化节点完成设置,域ID需要初始化RCL层完成设置
通过源码设置的命名空间优先级不如命令行参数的高,而源码设置的域ID优先级则高于外部输入
DDS作用域
ROS2环境变量ROS_DOMAIN_ID
,该变量继承自DDS体系
拥有相同域ID的所有应用程序的全局数据空间称为域,域之间相互独立,DDS在一个域内进行数据的发送和接收
若想在域之间通信,需设计桥梁来桥接域与域之间的数据,可参考ros2/domain_bridge
来实现
域ID是一个用来标识域的正整数,默认情况下,ROS2节点的域ID为0
一般,ROS2的域ID安全选择范围为[0,101]
DDS底层使用的是UDP,域ID所对应的是UDP端口,UDP端口是一个无符号的16bits整数,最大为65535
端口计算方法:
PortDM发现多播端口
PortUM用户多播端口
PortDU发现单播端口
PortUU用户多播端口
处理使用该计算方法外,由于最大端口的限制,及Linux平台的临时端口默认为32768~60999
除此外的端口都是可用的,可以推出Linux的可用域ID范围是0101和215232
如下使用命令设置域ID
export ROS_DOMAIN_ID=32
ros2 node list -a
通过编程来设置和查看当前域ID
在RCL层初始化时设置域ID,并通过rclcpp和rclpy提供接口
在节点中通过set_domain_id()
和context()
来设置和获取当前域ID
ROS2命名空间
通过标识符约束程序的作用域
命名空间命名规则
- 若不设置命名空间,则默认命名空间为
/
- 第一个字符为
/
,第二个字符为字母或下划线,其他位置字符可以是字母、下划线或数字 - 命名空间可叠加,如
/abc/def/h123
- 设置命名空间的节点,会将其创建的topic,service和action名称都添加到同一命名空间,但tf不会
命名空间可设置为多级,但其最大长度不可超过255(8U),即最大命名空间字符串长度、最大节点字符串和最大topic/service/action名字符串长度之和
命名空间可通过命令行引入,也可通过程序引入,在启动系统launch system中,还可通过launch文件引入
在ros2 run
中使用参数--ros-args
后添加--remap __ns:=/abc
来添加命名空间/abc
由程序引入时,通过节点构造函数参数进行设置,优先级低于命令行参数设置
命名空间和域ID区别
- 命名空间通过重映射将原本节点或其他参数重命名,并未做资源隔离,而域ID则是从网络的维度将资源隔离
- 不同命名空间的节点可以相互发现,但不同域的不行
- 命名空间原则上数量不受限,但域ID有数量限制
- 命名空间可通过初始化节点来设置,而域ID需要初始化RCL层来设置
- 通过源码设置的命名空间,优先级没有命令行参数的高,而源码设置的域ID优先级则高于外部输入
生命周期节点
为节点提供了状态切换功能,并实现了一套完备的节点状态机和管理机制,使ROS系统能更好的管理节点
生命周期节点是基于节点node
和服务service
实现的,节点生命周期描述了节点从生成、运行到销毁的过程
完整的周期包括4个主状态primary states,6个转换状态transition states和5种状态切换方法transition method
关于这些内容的消息定义,维护在ros2/rcl_interfaces
的lifecycle_msgs
中
主状态是指节点能稳定保持的状态
unconfigured
inactive
active
finalized
转换状态是指节点在主状态间切换的中间状态
configuring
cleaningup
shuttingdown
activation
deactivating
errorprocessing
状态转换方法
configure
cleanup
shutdown
activate
deactivate