ROS2:节点

节点

ROS2中,节点是一个抽象的实体,可以代表某类特定功能的抽象集合体,可以存在于进程或线程中
是ROS2的基础功能的载体,所有通信都需要通过节点来实现

节点和节点执行器

节点执行器executor是协调和调度节点运作的实体,并响应各类通信的回调结果
每个进程有一个或多个节点执行器,每个节点执行器有一个或多个节点

节点执行器有以下几种

  • 单线程节点执行器
    将所有已添加到维护队列的节点限制在一个线程内处理,占用一个线程资源,并根据其指定的规则对回调顺序和优先级进行设定
  • 多线程节点执行器
    负责管理的回调函数可以占用多个线程,根据设备性能,动态分配线程数,为队列中的节点处理回调,默认为CPU核心数
    std::thread::hardware_concurrency()获取线程最大数量
    线程回调的内容均来自于通信和等待任务,通信包含着订阅、服务、客户端和等待任务
  • 静态单线程节点执行器
    仅存在于rclcpp中,相对于普通单线程节点执行器可以有更低的CPU和内存占用率

ROS discourse社区的工程师每年在ROS论坛都会讨论新的节点执行器,为了提高其实效率和确定性等指标

建立节点

C++项目中,需要继承rclcpp::Node基类,Python项目中,需要继承rclcpp.Node基类

rclcpp默认方式是单线程节点执行器

节点命名规则

  • 不能为空
  • 第一个字符必须是字母、下划线、波浪号或斜杠
  • 后面的字符可以是字母、数字、下划线或斜杠
  • 使用下划线开头的节点是隐藏节点

节点执行器不会直接运行,需要通过节点执行器的spin()函数完成节点的所有回调工作
在rclcpp中提供了多种节点执行器的回调操作模式,常用的spinspin_allspin_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_dependenciesament构建体系中快速动态链接的方法
是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_interfaceslifecycle_msgs

主状态是指节点能稳定保持的状态
unconfigured
inactive
active
finalized

转换状态是指节点在主状态间切换的中间状态
configuring
cleaningup
shuttingdown
activation
deactivating
errorprocessing

状态转换方法
configure
cleanup
shutdown
activate
deactivate

posted @ 2024-11-01 19:54  sgqmax  阅读(25)  评论(0编辑  收藏  举报