【ROS2】Concept(Intermediate)

  • ROS_DOMAIN_ID:ROS 2域标识(用于隔离不同ROS 2系统的通信域,相同域标识的节点才能相互通信)
  • Different ROS 2 middleware vendors:不同的ROS 2中间件供应商(如提供DDS(Data Distribution Service,数据分发服务)实现的供应商,常见的有Fast DDS、Cyclone DDS等)
  • Logging and logger configuration:日志记录与日志配置(ROS 2系统中记录节点运行信息、错误提示等的功能,以及对日志输出格式、级别等的设置)
  • Quality of Service settings:服务质量(QoS)设置(用于定义ROS 2节点间数据传输的可靠性、时效性等指标,如“可靠传输”“-best effort(尽力而为)传输”等)
  • Executors:执行器(ROS 2中管理节点回调函数执行的组件,负责调度回调函数以处理消息、服务请求等事件)
  • Topic statistics:话题统计信息(用于监控ROS 2话题通信状态的数据,如消息发送/接收频率、延迟、丢失率等)
  • Overview and usage of RQt:RQt概述与使用方法(RQt是ROS 2的图形化工具框架,可通过插件实现日志查看、节点管理、数据可视化等功能)
  • Composition:组件化(ROS 2中一种将多个节点或功能模块打包到单个进程中运行的技术,可减少资源占用、提升通信效率)
  • Cross-compilation:交叉编译(在一台主机上为不同架构的目标设备(如嵌入式开发板)编译ROS 2程序的过程)
  • ROS 2 Security:ROS 2安全机制(用于保障ROS 2系统通信安全的功能集,包括数据加密、节点身份认证、访问控制等)
  • Tf2:变换库(ROS 2中用于处理坐标系变换的工具库,可实时计算不同节点/传感器之间的位置、姿态关系,如机器人关节、摄像头与底座的坐标系转换)

一、The ROS_DOMAIN_ID

包含 ROS Domain ID 计算器:
https://docs.ros.org/en/kilted/Concepts/Intermediate/About-Domain-ID.html
在这里插入图片描述
目录

  • 概述
  • 选择域标识(简短版)
  • 选择域标识(详细版)
  • 特定平台限制
  • 参与者限制
  • 域标识转UDP端口计算器

1、概述

正如其他文档中所阐述的,ROS 2用于通信的默认中间件是DDS(数据分发服务)。在DDS中,让不同逻辑网络共享同一物理网络的主要机制被称为“域标识(Domain ID)”。处于同一域的ROS 2节点可以自由地发现彼此并发送消息,而处于不同域的ROS 2节点则无法实现这一功能。默认情况下,所有ROS 2节点均使用域标识0。若要避免在同一网络中运行ROS 2的不同计算机组之间产生干扰,应为每个计算机组设置不同的域标识。

2、选择域标识(简短版)

下文将详细说明ROS 2中应使用的域标识范围的推导过程。若无需了解背景知识,仅需选择一个安全的域标识,直接选择0至101(含0和101)之间的任意数字即可。

3、选择域标识(详细版)

DDS会使用域标识来计算用于发现和通信的UDP端口,端口计算的详细方式可参考本文档。回顾基础网络知识可知,UDP端口是一个16位无符号整数,因此可分配的最高端口号为65535。根据前文提及文档中的公式进行计算,这意味着可分配的最高域标识为232,最低域标识为0。

3.1 特定平台限制

为实现最大兼容性,选择域标识时还应遵循一些额外的特定平台限制。尤其需要注意的是,最好避免将域标识分配到操作系统的临时端口(ephemeral port)范围内,这样可防止ROS 2节点使用的端口与计算机上其他网络服务的端口发生冲突。

以下是关于不同平台临时端口范围的说明:

操作系统临时端口范围说明
Linux默认情况下,Linux内核使用32768-60999作为临时端口。这意味着域标识0-101和215-232可安全使用,不会与临时端口冲突。Linux系统中可通过在/proc/sys/net/ipv4/ip_local_port_range中设置自定义值来配置临时端口范围,若使用自定义临时端口范围,则需相应调整上述域标识范围。
macOS默认情况下,macOS的临时端口范围为49152-65535。这意味着域标识0-166可安全使用,不会与临时端口冲突。macOS中可通过为net.inet.ip.portrange.firstnet.inet.ip.portrange.last设置自定义sysctl值来配置临时端口范围,若使用自定义临时端口范围,则需相应调整上述域标识范围。
Windows默认情况下,Windows的临时端口范围为49152-65535。这意味着域标识0-166可安全使用,不会与临时端口冲突。Windows中可通过netsh工具配置临时端口范围,若使用自定义临时端口范围,则需相应调整上述域标识范围。

3.2 参与者限制

计算机上运行的每个ROS 2进程,都会创建一个DDS“参与者(participant)”。由于每个DDS参与者会占用计算机上的两个端口,因此在一台计算机上运行超过120个ROS 2进程时,可能会溢出到其他域标识对应的端口或临时端口。

具体原因可通过域标识1和域标识2的示例说明:

  • 域标识1使用7650和7651端口进行多播(multicast)。
  • 域标识2使用7900和7901端口进行多播。
  • 在域标识1中创建第1个进程(即第0个参与者)时,会使用7660和7661端口进行单播(unicast)。
  • 在域标识1中创建第120个进程(即第119个参与者)时,会使用7898和7899端口进行单播。
  • 在域标识1中创建第121个进程(即第120个参与者)时,会使用7900和7901端口进行单播,而这两个端口与域标识2的多播端口重叠。

若确定某台计算机某一时刻只会处于单个域标识下,且该域标识数值足够小,则在这台计算机上创建超过120个ROS 2进程是安全的。

当选择的域标识接近特定平台域标识范围的上限时,还需考虑一项额外限制。

例如,假设某台Linux计算机使用域标识101:

  • 该计算机上第0个ROS 2进程会连接32650、32651、32660和32661端口。
  • 该计算机上第1个ROS 2进程会连接32650、32651、32662和32663端口。
  • 该计算机上第53个ROS 2进程会连接32650、32651、32766和32767端口。
  • 该计算机上第54个ROS 2进程会连接32650、32651、32768和32769端口,而这两个端口已进入临时端口范围。

因此,在Linux系统上使用域标识101时,应创建的最大进程数为54。同理,在Linux系统上使用域标识232时,应创建的最大进程数为63,因为最高端口号为65535。

macOS和Windows系统的情况类似,但具体数值有所不同。在macOS和Windows系统中,当选择166(该平台域标识范围的上限)作为域标识时,一台计算机上在端口进入临时端口范围前,可创建的ROS 2进程最大数量为120。

3.3域标识转UDP端口计算器

  • 域标识(Domain ID):0
  • 参与者标识(Participant ID):0
  • 发现多播端口(Discovery Multicast Port):7400
  • 用户多播端口(User Multicast Port):7401
  • 发现单播端口(Discovery Unicast Port):7410
  • 用户单播端口(User Unicast Port):7411

二、Different ROS 2 middleware vendors

https://design.ros2.org/articles/ros_on_dds.html

目录

  • DDS中间件
  • Zenoh中间件
  • 受支持的RMW实现
  • 选择中间件实现
  • 多种RMW实现
  • DDS中间件间的跨供应商通信

ROS 2支持多种中间件实现,这些实现可提供发现、序列化和传输通信功能。之所以具备这种灵活性,是因为在选择中间件供应商/实现时,并非“一刀切”的方案就一定适用。ROS 2生态系统最初以DDS/RTPS为中间件基础构建,如今已扩展到包含Zenoh等其他中间件架构。

1、DDS中间件

DDS(数据分发服务)是一项行业标准,有多家供应商提供其实现版本,例如RTI的Connext DDS、eProsima的Fast DDS、Eclipse的Cyclone DDS以及GurumNetworks的GurumDDS。RTPS(又称DDSI-RTPS)是DDS用于网络通信的有线协议。

本文将详细阐述使用DDS实现和/或DDS的RTPS有线协议的背后原因。总而言之,DDS是一种端到端中间件,具备与ROS系统相关的功能,例如分布式发现(不同于ROS 1中的集中式发现)以及对传输的各种“服务质量”(QoS)选项的控制能力。

2、Zenoh中间件

Zenoh是一种融合了互联网级发布/订阅与分布式查询的协议。它设计用于多种部署场景下的高效通信,涵盖从服务器级硬件和网络到资源受限的边缘设备。Zenoh将位置透明性的优势扩展到存储数据,使得发起查询时无需关注数据的存储位置。

作为ROS 2的一种RMW(ROS中间件接口)实现,Zenoh为DDS提供了更轻量级的替代方案,同时保留了服务质量(QoS)功能(在Zenoh中,本质上不存在“不兼容”的QoS设置)。其极小的有线开销和灵活的路由功能,使Zenoh非常适合复杂的网络环境。

3、受支持的RMW实现

产品名称(Product name)许可证(License)RMW实现(RMW implementation)状态(Status)
eProsima Fast DDSApache 2 许可证rmw_fastrtps_cpp完全支持。默认RMW,随二进制发行版打包提供。
Eclipse Cyclone DDSEclipse Public License v2.0(EPL v2.0)rmw_cyclonedds_cpp完全支持,随二进制发行版打包提供。
RTI Connext DDS商业许可证、研究许可证rmw_connextdds完全支持。二进制文件中包含相关支持,但Connext需单独安装。
GurumNetworks GurumDDS商业许可证rmw_gurumdds_cpp社区支持。二进制文件中包含相关支持,但GurumDDS需单独安装。
Eclipse ZenohEclipse Public License v2.0(EPL v2.0)rmw_zenoh_cpp完全支持。从Kilted Kaiju版本开始,随二进制发行版打包提供。

有关使用多种RMW实现的实用信息,请参阅“使用多种RMW实现”教程。

4、选择中间件实现

选择中间件实现时,需考虑多个因素:既有许可证等后勤方面的因素,也有平台可用性、资源利用率、计算占用空间等技术方面的因素。供应商可能会提供多种中间件实现,以满足不同的需求。

例如,RTI的Connext实现有多个变体,用途各不相同——有的专门针对微控制器,有的则针对需要特殊安全认证的应用(目前我们仅支持其标准桌面版本)。Eclipse同时提供Cyclone DDS和Zenoh:Cyclone DDS是较轻量化的DDS实现之一,针对实时确定性通信进行了优化;而Zenoh则面向物联网和边缘计算场景,这类场景中,高吞吐量、低延迟以及异构环境下的互操作性是首要考量因素。

若要在ROS 2中使用某一中间件实现,需创建一个“ROS中间件接口”(又称rmw接口或简称为rmw)包,该包需借助特定实现的API和工具,来实现抽象的ROS中间件接口。为支持各种中间件实现而开发和维护RMW包需要大量工作,但这种多样性至关重要——它能确保ROS 2代码库不依赖于任何单一实现,用户可根据项目需求更换不同的实现。

5、多种RMW实现

当前活跃发行版的ROS 2二进制发行版,默认内置支持多种RMW实现(包括Fast DDS、RTI Connext Pro、Eclipse Cyclone DDS、GurumNetworks GurumDDS)。从ROS 2 Kilted Kaiju版本开始,还新增了对Eclipse Zenoh的支持。默认的RMW实现是Fast DDS,无需额外安装步骤即可使用,因为它已随二进制包一同分发。

除Fast DDS外,Cyclone DDS、Connext、GurumDDS等其他RMW实现,可通过安装额外的包来启用,无需重新构建任何内容或替换现有包。

从源码构建的ROS 2工作空间,可同时构建并安装多种RMW实现。在编译ROS 2核心代码时,若能找到相关的RMW实现,且对应的中间件实现已正确安装、相关环境变量已配置,那么该RMW实现就会被构建。例如,若工作空间中包含RTI Connext DDS的RMW包代码,且能找到RTI Connext Pro的安装文件,那么该RMW包就会被构建。

若ROS 2工作空间中存在多种RMW实现,且Fast DDS可用,则默认选择Fast DDS作为RMW实现。若未安装Fast DDS,则会根据包标识符的字母顺序选择默认中间件。这里的“实现标识符”指的是提供RMW实现的ROS包名称,例如rmw_cyclonedds_cpp。举例来说,若同时安装了rmw_cyclonedds_cpprmw_connextdds这两个ROS包,那么rmw_connextdds会成为默认实现;而一旦安装了rmw_fastrtps_cpp,它就会成为默认实现。

有关运行ROS 2示例时如何指定使用的RMW实现,请参阅相关指南

6、DDS中间件间的跨供应商通信

在很多情况下,使用不同DDS中间件实现的节点能够实现通信,但并非所有情况下都能如此。尽管不同的DDS实现在有限场景下可能具备兼容性,但这种兼容性并不能得到保证。因此,建议用户确保分布式系统的所有组件都使用相同的ROS版本和相同的RMW实现。

三、Logging and logger configuration

目录

  • 概述
  • 严重级别
  • 编程接口(APIs)
  • 配置
    • 环境变量
    • 节点创建
  • 日志子系统设计
    • rcutils
    • rcl_logging_spdlog
    • rcl
    • rclcpp
    • rclpy
  • 日志使用

1、概述

ROS 2中的日志子系统旨在将日志消息传递到多种目标,包括:

  • 控制台(若已连接)
  • 磁盘上的日志文件(若有本地存储可用)
  • ROS 2网络中的/rosout话题

默认情况下,ROS 2节点中的日志消息会输出到控制台(通过标准错误流stderr)、磁盘上的日志文件以及ROS 2网络的/rosout话题。所有这些目标均可按节点单独启用或禁用。

本文档后续将详细介绍日志子系统背后的相关概念。

2、严重级别

日志消息均关联一个严重级别,按严重程度从低到高依次为:DEBUG(调试)、INFO(信息)、WARN(警告)、ERROR(错误)、FATAL(致命)。

日志记录器(logger)仅会处理严重级别大于或等于其指定级别的日志消息。

每个节点都关联一个日志记录器,该记录器会自动包含节点的名称和命名空间。若节点名称通过外部重映射改为源码中定义之外的名称,此变更会反映在日志记录器名称中。此外,也可创建使用特定名称的非节点日志记录器。

日志记录器名称具有层级结构。若名为“abc.def”的日志记录器未设置级别,会继承其父级“abc”的级别;若父级级别也未设置,则使用默认日志记录器级别。当“abc”的级别被修改时,其所有子级(如“abc.def”“abc.ghi.jkl”)的级别都会受到影响,除非子级已明确设置自身级别。

3、编程接口(APIs)

以下是ROS 2日志基础设施的终端用户应使用的编程接口,按客户端库分类说明。

C++

  • RCLCPP_{DEBUG,INFO,WARN,ERROR,FATAL}:每次执行到该行代码时,输出指定的printf格式消息
  • RCLCPP_{DEBUG,INFO,WARN,ERROR,FATAL}_ONCE:仅在第一次执行到该行代码时,输出指定的printf格式消息
  • RCLCPP_{DEBUG,INFO,WARN,ERROR,FATAL}_EXPRESSION:仅当指定表达式为true时,输出指定的printf格式消息
  • RCLCPP_{DEBUG,INFO,WARN,ERROR,FATAL}_FUNCTION:仅当指定函数返回true时,输出指定的printf格式消息
  • RCLCPP_{DEBUG,INFO,WARN,ERROR,FATAL}_SKIPFIRST:除第一次执行外,每次执行到该行代码时均输出指定的printf格式消息
  • RCLCPP_{DEBUG,INFO,WARN,ERROR,FATAL}_THROTTLE:以指定的整数毫秒(ms)频率为上限,输出指定的printf格式消息(即消息输出频率不超过该上限)
  • RCLCPP_{DEBUG,INFO,WARN,ERROR,FATAL}_SKIPFIRST_THROTTLE:以指定的整数毫秒(ms)频率为上限输出指定的printf格式消息,且跳过第一次输出
  • RCLCPP_{DEBUG,INFO,WARN,ERROR,FATAL}_STREAM:每次执行到该行代码时,输出指定的C++流格式消息
  • RCLCPP_{DEBUG,INFO,WARN,ERROR,FATAL}_STREAM_ONCE:仅在第一次执行到该行代码时,输出指定的C++流格式消息
  • RCLCPP_{DEBUG,INFO,WARN,ERROR,FATAL}_STREAM_EXPRESSION:仅当指定表达式为true时,输出指定的C++流格式消息
  • RCLCPP_{DEBUG,INFO,WARN,ERROR,FATAL}_STREAM_FUNCTION:仅当指定函数返回true时,输出指定的C++流格式消息
  • RCLCPP_{DEBUG,INFO,WARN,ERROR,FATAL}_STREAM_SKIPFIRST:除第一次执行外,每次执行到该行代码时均输出指定的C++流格式消息
  • RCLCPP_{DEBUG,INFO,WARN,ERROR,FATAL}_STREAM_THROTTLE:以指定的整数毫秒(ms)频率为上限,输出指定的C++流格式消息
  • RCLCPP_{DEBUG,INFO,WARN,ERROR,FATAL}_STREAM_SKIPFIRST_THROTTLE:以指定的整数毫秒(ms)频率为上限输出指定的C++流格式消息,且跳过第一次输出

上述每个接口均将rclcpp::Logger对象作为第一个参数。该对象可通过调用节点接口node->get_logger()获取(推荐方式),也可通过构建独立的rclcpp::Logger对象获取。

  • rcutils_logging_set_logger_level:为特定名称的日志记录器设置严重级别
  • rcutils_logging_get_logger_effective_level:根据日志记录器名称,返回其级别(可能未设置)

Python

  • logger.{debug,info,warning,error,fatal}:将指定的Python字符串输出到日志基础设施。调用时可通过以下关键字参数控制行为:
    • throttle_duration_sec:若不为None,指限流时间间隔(单位:浮点型秒)
    • skip_first:若为True,除第一次执行外,每次执行到该行代码时均输出消息
    • once:若为True,仅在第一次执行到该行代码时输出消息
  • rclpy.logging.set_logger_level:为特定名称的日志记录器设置严重级别
  • rclpy.logging.get_logger_effective_level:根据日志记录器名称,返回其级别(可能未设置)

4、配置

由于rclcpprclpy使用相同的底层日志基础设施,因此二者的配置选项一致。

环境变量

以下环境变量可控制ROS 2日志记录器的部分行为。需注意,这些环境变量的设置对进程全局生效,即适用于进程中的所有节点。

  • ROS_LOG_DIR:控制用于将日志消息写入磁盘的日志目录(若已启用磁盘写入功能)。若该变量非空,直接使用其指定的目录;若为空,则使用ROS_HOME环境变量的值构建路径,格式为$ROS_HOME/.log。在所有情况下,~字符都会展开为用户的主目录(HOME目录)。

  • ROS_HOME:控制用于存储各类ROS文件(包括日志文件和配置文件)的主目录。在日志相关场景中,该变量用于构建日志文件的存储目录路径。若该变量非空,ROS_HOME路径即使用其指定的值;所有情况下,~字符都会展开为用户的主目录。

  • RCUTILS_LOGGING_USE_STDOUT:控制日志消息输出到的流。若未设置或值为0,使用标准错误流(stderr);若值为1,使用标准输出流(stdout)。

  • RCUTILS_LOGGING_BUFFERED_STREAM:控制日志流(由RCUTILS_LOGGING_USE_STDOUT配置)应为行缓冲还是无缓冲。若未设置,使用流的默认缓冲方式(通常stdout为行缓冲,stderr为无缓冲);若值为0,强制流为无缓冲;若值为1,强制流为行缓冲。

  • RCUTILS_COLORIZED_OUTPUT:控制输出消息时是否使用颜色。若未设置,将根据平台和控制台是否为TTY(终端设备)自动判断;若值为0,强制禁用颜色输出;若值为1,强制启用颜色输出。

  • RCUTILS_CONSOLE_OUTPUT_FORMAT:控制每条日志消息输出的字段。可用字段如下:

    • {severity}:严重级别
    • {name}:日志记录器名称(可能为空)
    • {message}:日志消息内容(可能为空)
    • {function_name}:调用日志接口的函数名称(可能为空)
    • {file_name}:调用日志接口的文件名称(可能为空)
    • {time}:从纪元时间(epoch time)开始的秒数
    • {time_as_nanoseconds}:从纪元时间开始的纳秒数
    • {date_time_with_ms}:ISO格式的时间,例如2024-06-11 09:29:19.304
    • {line_number}:调用日志接口的代码行号(可能为空)

    若未指定格式,默认使用[{severity}] [{time}] [{name}]:{message}

RCUTILS_CONSOLE_OUTPUT_FORMAT还支持以下转义字符语法:

转义字符语法表示的字符
\a警报符(Alert)
\b退格符(Backspace)
\n换行符(New line)
\r回车符(Carriage return)
\t水平制表符(Horizontal tab)

节点创建

初始化ROS 2节点时,可通过节点选项控制其部分日志行为。由于这些选项是按节点设置的,因此即使多个节点组合到同一个进程中,也可为不同节点设置不同的日志选项。

  • log_levels:为该节点内某个组件设置的日志级别。设置命令示例:ros2 run demo_nodes_cpp talker --ros-args --log-level talker:=DEBUG
  • external_log_config_file:用于配置后端日志记录器的外部文件。若该值为NULL,将使用默认配置。需注意,该文件的格式与后端相关(目前默认后端日志记录器spdlog尚未实现此功能)。设置命令示例:ros2 run demo_nodes_cpp talker --ros-args --log-config-file log-config.txt
  • log_stdout_disabled:是否禁用将日志消息输出到控制台。设置命令示例:ros2 run demo_nodes_cpp talker --ros-args --disable-stdout-logs
  • log_rosout_disabled:是否禁用将日志消息输出到/rosout话题。禁用后可显著节省网络带宽,但外部观察者将无法监控日志。设置命令示例:ros2 run demo_nodes_cpp talker --ros-args --disable-rosout-logs
  • log_ext_lib_disabled:是否完全禁用外部日志记录器。在某些情况下,禁用后速度可能更快,但日志将无法写入磁盘。设置命令示例:ros2 run demo_nodes_cpp talker --ros-args --disable-external-lib-logs

5、日志子系统设计

下图展示了日志子系统的五个主要组成部分及其交互方式(文本示意如下):
在这里插入图片描述

总结:核心 rcl, 控制传到哪个类型, rcl_logging_spdlog 写入磁盘 ; rcutils 写入到控制台 ; 通过rwm发送到ros2 /rosout话题。

rcutils

rcutils包含日志实现,可根据特定格式(见上文“配置”部分)对日志消息进行格式化,并将其输出到控制台。rcutils实现了完整的日志解决方案,但允许更高层级的组件通过依赖注入模式接入日志基础设施。这一点在下文介绍rcl层时会更清晰。

需注意,rcutils是进程级的日志实现,因此在此层级配置的任何设置都会影响整个进程,而非单个节点。

rcl_logging_spdlog

rcl_logging_spdlog实现了rcl_logging_interface接口,从而为rcl层提供外部日志服务。具体而言,rcl_logging_spdlog会对格式化后的日志消息进行处理,并使用spdlog库将其写入磁盘上的日志文件,默认存储路径通常为~/.ros/log(路径可配置,见上文“配置”部分)。

rcl

rcl中的日志子系统依赖rcutilsrcl_logging_spdlog提供ROS 2的大部分日志服务。当日志消息传入时,rcl会决定消息的发送目标。日志消息主要可发送至以下3个目标,单个节点可启用这些目标的任意组合:

  • 通过rcutils层发送到控制台
  • 通过rcl_logging_spdlog层发送到磁盘
  • 通过RMW层发送到ROS 2网络的/rosout话题

rclcpp

rclcpp是基于rcl接口构建的主要ROS 2 C++ API。在日志场景中,rclcpp提供了RCLCPP_系列日志宏(完整列表见上文“编程接口”部分)。当执行某个RCLCPP_宏时,会先将节点当前的严重级别与该宏的严重级别进行比较:若宏的严重级别大于或等于节点的严重级别,消息会被格式化并输出到所有当前已配置的目标。需注意,rclcpp为日志调用使用全局互斥锁,因此同一进程内的所有日志调用最终都是单线程执行的。

rclpy

rclpy是基于rcl接口构建的主要ROS 2 Python API。在日志场景中,rclpy提供了logger.debug类函数(完整列表见上文“编程接口”部分)。当执行某个logger.debug函数时,会先将节点当前的严重级别与该函数的严重级别进行比较:若函数的严重级别大于或等于节点的严重级别,消息会被格式化并输出到所有当前已配置的目标。

6、日志使用

C++

  • 参考rclcpp日志演示代码,获取简单示例。

Python

  • 参考日志演示代码,了解使用示例。
  • 参考rclpy示例,了解节点日志记录器的使用方法。
  • 参考rclpy测试代码,了解关键字参数(如skip_firstonce)的使用示例。

四、Quality of Service settings

目录

  • 概述
  • QoS策略
    • 与ROS 1的对比(QoS策略相关)
  • QoS配置文件
  • QoS兼容性
    • 与ROS 1的对比(QoS兼容性相关)
  • QoS事件
  • 匹配事件

1、概述

ROS 2提供了丰富多样的服务质量(Quality of Service,QoS)策略,可用于调整节点间的通信方式。通过合理配置QoS策略,ROS 2的通信既可以像TCP那样可靠,也可以像UDP那样采用“尽力而为”模式,还能实现介于两者之间的多种通信状态。与主要仅支持TCP的ROS 1不同,ROS 2借助底层DDS传输的灵活性,能适应更多场景——例如在丢包率较高的无线网络中,“尽力而为”策略更为适用;在实时计算系统中,则可通过合适的QoS配置文件满足任务截止时间要求。

一组QoS“策略”组合形成一个QoS“配置文件(profile)”。考虑到为特定场景选择合适QoS策略的复杂性,ROS 2为常见用例(如传感器数据传输)提供了一组预定义的QoS配置文件。同时,开发者也可灵活控制QoS配置文件中的特定策略。

QoS配置文件可应用于发布者(publisher)、订阅者(subscription)、服务端(service server)和客户端(client)。上述每个实体的实例均可独立应用QoS配置文件,但如果使用不同的配置文件,可能会出现兼容性问题,导致消息无法传递。

2、QoS策略

当前基础QoS配置文件包含以下策略的设置项:

历史记录(History)

  • 保留最新(Keep last):仅存储最新的N个样本,可通过队列深度(queue depth)选项配置N的值。
  • 保留全部(Keep all):存储所有样本,但受底层中间件的资源限制配置约束。

深度(Depth)

  • 队列大小:仅当“历史记录”策略设置为“保留最新”时生效。

可靠性(Reliability)

  • 尽力而为(Best effort):尝试传递样本,但在网络不稳定时可能会丢失样本。
  • 可靠传输(Reliable):确保样本被传递,必要时可多次重试。

持久性(Durability)

  • 本地暂存(Transient local):发布者负责为“后期加入”的订阅者持久化存储样本。
  • 易失性(Volatile):不尝试持久化存储样本。

截止时间(Deadline)

  • 时长(Duration):话题后续消息发布的预期最大时间间隔。

生命周期(Lifespan)

  • 时长(Duration):消息从发布到接收的最大允许时间,超过该时间的消息会被视为过期(过期消息将被静默丢弃,相当于从未被接收)。

活跃度(Liveliness)

  • 自动(Automatic):当节点的任意一个发布者发布消息时,系统会将该节点的所有发布者的“租约时长”(lease duration)重新计时,认为它们仍处于活跃状态。
  • 按话题手动(Manual by topic):只有当发布者通过调用发布者API手动声明自身仍活跃时,系统才会将其“租约时长”重新计时,认为该发布者仍活跃。

租约时长(Lease Duration)

  • 时长(Duration):发布者需在该时间周期内表明自身活跃,否则系统会判定其失去活跃度(失去活跃度可能意味着发布者出现故障)。

对于非时长类型的策略,均存在“系统默认(system default)”选项,即使用底层中间件的默认设置;对于时长类型的策略,均存在“默认(default)”选项,代表时长未指定,底层中间件通常会将其解读为无限长的时长。

与ROS 1的对比(QoS策略相关)

  • ROS 2中的“历史记录(History)”和“深度(Depth)”策略组合,实现了类似ROS 1中队列大小(queue size)的功能。
  • ROS 2中的“可靠性(Reliability)”策略,与ROS 1中的UDPROS(仅在roscpp中支持,对应“尽力而为”)或TCPROS(ROS 1默认方式,对应“可靠传输”)功能类似。但需注意,ROS 2中的“可靠传输”策略即便基于UDP实现,也能在合适场景下支持多播。
  • ROS 2中“本地暂存(Transient local)”类型的“持久性(Durability)”策略,结合任意深度设置,可实现类似ROS 1中“ latch 发布者”(即持久化发布者)的功能。
  • ROS 2中的其余QoS策略,在ROS 1中无对应功能,这意味着ROS 2在服务质量控制方面比ROS 1更丰富。未来ROS 2可能还会新增更多QoS策略。

3、QoS配置文件

配置文件让开发者可专注于应用本身,无需关注所有可能的QoS设置。一个QoS配置文件定义了一组策略,这些策略经过搭配,适用于特定用例。

目前已定义的QoS配置文件如下:

发布者与订阅者的默认QoS设置

为简化从ROS 1到ROS 2的迁移,ROS 2设计了与ROS 1类似的网络行为。默认情况下,ROS 2中发布者和订阅者的QoS策略设置为:历史记录为“保留最新(Keep last)”、队列大小为10,可靠性为“可靠传输(Reliable)”,持久性为“易失性(Volatile)”,活跃度为“系统默认(system default)”;截止时间、生命周期和租约时长均设为“默认(default)”。

服务(Services)

与发布者、订阅者类似,服务的可靠性策略设为“可靠传输(Reliable)”。服务必须使用“易失性(Volatile)”持久性策略,这一点尤为重要——若使用其他持久性策略,服务端重启后可能会接收过期的请求。尽管客户端可避免接收重复响应,但服务端无法避免处理过期请求带来的副作用。

传感器数据(Sensor data)

对于传感器数据,多数场景下“及时接收数据”比“确保所有数据都被接收”更重要。也就是说,开发者更希望尽快获取最新采集的样本,即便可能丢失部分数据。因此,传感器数据配置文件采用“尽力而为(Best effort)”可靠性策略,并设置较小的队列大小。

参数(Parameters)

ROS 2中的参数基于服务实现,因此其QoS配置文件与服务类似。不同之处在于,参数配置文件使用更大的队列深度——这样即使参数客户端无法连接到参数服务端,请求也不会丢失。

系统默认(System default)

该配置文件对所有策略均使用RMW(ROS中间件接口)实现的默认值,不同RMW实现的默认值可能不同。

点击此处查看上述配置文件所使用的具体策略。这些配置文件的设置可能会根据社区反馈进一步调整。

3、QoS兼容性

:本节以发布者和订阅者为例进行说明,但内容同样适用于服务端和客户端。

发布者和订阅者的QoS配置文件可独立配置,仅当两者的QoS配置文件兼容时,才能建立连接。

QoS配置文件的兼容性基于“请求 vs 提供(Request vs Offered)”模型判断:订阅者请求的QoS配置文件代表其“可接受的最低质量”,发布者提供的QoS配置文件代表其“能提供的最高质量”。仅当请求方QoS配置文件的每一项策略都不比提供方的策略更严格时,两者才能建立连接。

单个发布者可同时与多个订阅者建立连接,即便这些订阅者的请求QoS配置文件不同。某一对发布者与订阅者的兼容性,不受其他发布者或订阅者的影响。

以下表格展示了不同策略设置的兼容性及结果:

可靠性QoS策略兼容性

发布者(提供方)订阅者(请求方)兼容(Compatible)
尽力而为尽力而为
尽力而为可靠传输
可靠传输尽力而为
可靠传输可靠传输

持久性QoS策略兼容性

发布者(提供方)订阅者(请求方)兼容(Compatible)结果(Result)
易失性易失性仅接收新消息
易失性本地暂存无通信
本地暂存易失性仅接收新消息
本地暂存本地暂存接收新消息和历史消息

若要实现“后期订阅者可看到消息”的“ latch 话题”功能,发布者和订阅者必须均同意使用“本地暂存(Transient Local)”持久性策略。

截止时间QoS策略兼容性

假设x和y为任意有效的时长值。

发布者(提供方)订阅者(请求方)兼容(Compatible)
默认默认
默认x
x默认
xx
xy(y > x)
xy(y < x)

活跃度QoS策略兼容性

发布者(提供方)订阅者(请求方)兼容(Compatible)
自动自动
自动按话题手动
按话题手动自动
按话题手动按话题手动

租约时长QoS策略兼容性

假设x和y为任意有效的时长值。

发布者(提供方)订阅者(请求方)兼容(Compatible)
默认默认
默认x
x默认
xx
xy(y > x)
xy(y < x)

只有当所有影响兼容性的策略均兼容时,发布者与订阅者才能建立连接。例如,即便某对请求与提供的QoS配置文件在可靠性策略上兼容,但在持久性策略上不兼容,两者仍无法建立连接。

若无法建立连接,发布者与订阅者之间将不会传递任何消息。ROS 2提供了检测此类情况的机制,将在后续章节介绍。

与ROS 1的对比(QoS兼容性相关)

在ROS 1中,只要发布者和订阅者的消息类型相同且话题名称一致,就能建立连接。而在ROS 2中,需注意请求与提供的QoS配置文件可能存在不兼容的情况,这是ROS 2相较于ROS 1的新特性。

4、QoS事件

部分QoS策略会关联特定事件。开发者可为每个发布者和订阅者设置回调函数,当这些QoS事件触发时,回调函数会被执行——其处理方式与处理话题接收消息类似,开发者可根据需求自定义逻辑。

发布者可订阅的QoS事件

  • 提供方截止时间超时(Offered deadline missed):发布者未在截止时间QoS策略设定的预期时长内发布消息。
  • 活跃度丢失(Liveliness lost):发布者未在租约时长内表明自身活跃。
  • 提供方QoS不兼容(Offered incompatible QoS):发布者遇到同一话题上的订阅者请求的QoS配置文件超出自身提供能力,导致两者无法建立连接。

订阅者可订阅的QoS事件

  • 请求方截止时间超时(Requested deadline missed):订阅者未在截止时间QoS策略设定的预期时长内接收消息。
  • 活跃度变化(Liveliness changed):订阅者检测到同一话题上的一个或多个发布者未在租约时长内表明自身活跃。
  • 请求方QoS不兼容(Requested incompatible QoS):订阅者遇到同一话题上的发布者提供的QoS配置文件无法满足自身请求,导致两者无法建立连接。

5、匹配事件

除QoS事件外,当发布者与订阅者建立或断开连接时,会生成“匹配事件(Matched events)”。开发者可为每个发布者和订阅者设置回调函数,当匹配事件触发时,回调函数会被执行——其处理方式与处理话题接收消息类似,开发者可根据需求自定义逻辑。

发布者和订阅者均可订阅匹配事件:

  • 发布者:当发现话题匹配且QoS兼容的订阅者,或与已连接的订阅者断开连接时,触发匹配事件。
  • 订阅者:当发现话题匹配且QoS兼容的发布者,或与已连接的发布者断开连接时,触发匹配事件。

以下演示代码展示了如何使用匹配事件:

  • rclcpp:演示代码
  • rclpy:演示代码

五、Executors

目录

  1. 概述
  2. 基本使用
  3. 执行器类型
  4. 回调组
  5. 调度语义
  6. 未来展望

1. 概述

ROS 2中的执行管理由执行器(Executor)负责。执行器会借助底层操作系统的一个或多个线程,在接收到消息与事件时,调用订阅(subscription)、计时器(timer)、服务端(service server)、动作服务端(action server)等的回调函数。

显式的执行器类(在rclcpp中定义于executor.hpp,在rclpy中定义于executors.py,在rclc中定义于executor.h),相比ROS 1中的spin机制,能提供更强的执行管理控制能力,不过两者的基础API极为相似。

下文将重点围绕C++客户端库rclcpp展开介绍。

2. 基本使用

在最简单的场景中,可通过调用rclcpp::spin(..),让主线程处理节点的传入消息与事件,代码如下:

int main(int argc, char* argv[])
{
   // 一些初始化操作
   rclcpp::init(argc, argv);
   ...

   // 实例化一个节点
   rclcpp::Node::SharedPtr node = ...

   // 运行执行器
   rclcpp::spin(node);

   // 关闭并退出
   ...
   return 0;
}

调用spin(node)本质上等同于实例化并调用“单线程执行器(Single-Threaded Executor)”——这是最简单的执行器类型,其展开后的代码如下:

rclcpp::executors::SingleThreadedExecutor executor;
executor.add_node(node);
executor.spin();

当调用执行器实例的spin()方法后,当前线程会开始查询rcl层与中间件层的传入消息及其他事件,并调用对应的回调函数,直至节点关闭。

为避免与中间件的QoS设置产生冲突,传入的消息不会存储在客户端库层的队列中,而是保留在中间件内,直到回调函数获取消息并进行处理(这是与ROS 1的关键区别)。系统通过“等待集(wait set)”告知执行器中间件层是否存在可用消息,每个队列对应一个二进制标志;同时,等待集还可用于检测计时器是否超时。

在这里插入图片描述

此外,组件的容器进程(即所有无需显式main函数就能创建并执行节点的场景),同样会使用单线程执行器。

3. 执行器类型

目前,rclcpp提供三种执行器类型,均继承自同一个父类,具体继承关系如下:
在这里插入图片描述

其中,多线程执行器会创建数量可配置的线程,从而支持并行处理多个消息或事件。

注意:静态单线程执行器已被弃用,建议改用单线程执行器。静态单线程执行器的设计初衷是降低节点实体(如订阅、计时器、服务端、动作服务端等)的扫描运行时开销,如今所有其他执行器也已具备这一运行时优化特性。此外,静态单线程执行器存在一些问题(例如在spin_some中不遵守最大时长限制),因此部分单元测试会跳过该执行器。更多细节可参考ROS论坛文章:《The ROS 2 C++ Executors》。

通过为每个节点调用add_node(..)方法,这三种执行器都可用于管理多个节点,示例代码如下:

rclcpp::Node::SharedPtr node1 = ...
rclcpp::Node::SharedPtr node2 = ...
rclcpp::Node::SharedPtr node3 = ...

rclcpp::executors::SingleThreadedExecutor executor;
executor.add_node(node1);
executor.add_node(node2);
executor.add_node(node3);
executor.spin();

在上述示例中,单线程执行器通过一个线程同时为三个节点提供服务。若使用多线程执行器,实际的并行处理能力则取决于回调组的配置。

4. 回调组

ROS 2允许将节点的回调函数组织成“回调组(callback group)”。在rclcpp中,可通过节点类的create_callback_group函数创建回调组;在rclpy中,可通过调用特定回调组类型的构造函数创建。

在节点的整个执行过程中,必须保持回调组的存在(例如将其作为类成员存储),否则执行器无法触发该组中的回调函数。创建订阅、计时器等对象时,可指定其所属的回调组(例如通过订阅选项设置),具体代码示例如下:

C++示例

my_callback_group = create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive);

rclcpp::SubscriptionOptions options;
options.callback_group = my_callback_group;

my_subscription = create_subscription<Int32>("/topic", rclcpp::SensorDataQoS(),
                                             callback, options);

Python示例

my_callback_group = MutuallyExclusiveCallbackGroup()
my_subscription = self.create_subscription(Int32, "/topic", self.callback, qos_profile=1,
                                           callback_group=my_callback_group)

未指定回调组的订阅、计时器等对象,会被自动分配到“默认回调组”。在rclcpp中,可通过NodeBaseInterface::get_default_callback_group()获取默认回调组;在rclpy中,可通过Node.default_callback_group获取。

回调组在实例化时需指定类型,共分为两种:

  • 互斥型(Mutually exclusive):该组中的回调函数不得并行执行。
  • 可重入型(Reentrant):该组中的回调函数可并行执行。

不同回调组的回调函数始终能并行执行。多线程执行器会将其线程作为线程池,依据上述规则尽可能并行处理更多回调函数。关于如何高效使用回调组,可参考《Using Callback Groups》(回调组使用指南)。

另外,rclcpp中的执行器父类还提供add_callback_group(..)函数,支持将不同回调组分配给不同执行器。通过操作系统调度器配置底层线程,可让特定回调函数的优先级高于其他回调函数——例如,可将控制循环的订阅和计时器优先级设为高于节点的其他订阅和标准服务。examples_rclcpp_cbg_executor包提供了该机制的演示示例。

5. 调度语义

若回调函数的处理时间短于消息和事件的发生周期,执行器基本会按“先进先出(FIFO)”顺序处理它们。但如果部分回调函数的处理时间较长,消息和事件就会在栈的底层队列中堆积。

等待集机制仅向执行器反馈极少的队列信息——具体来说,仅告知某一话题是否存在消息。执行器会基于此信息,以“轮询(round-robin)”方式处理消息(包括服务和动作相关消息),而非按FIFO顺序。

(调度语义流程图)
在这里插入图片描述

该调度语义最初由Casini等人在2019年ECRTS(欧洲实时系统会议)的一篇论文中提出。(注:该论文还提及计时器事件的优先级高于所有其他消息,但这一优先级设置在Eloquent版本中已被移除。)

6. 未来展望

尽管rclcpp的三种执行器能满足大多数应用场景的需求,但它们仍存在一些问题,使其无法适用于实时应用——实时应用通常要求明确的执行时间、确定性以及对执行顺序的自定义控制。以下是主要问题的总结:

  1. 调度语义复杂且混乱,而理想情况下需要明确的调度语义以进行正式的时序分析。
  2. 回调函数可能出现优先级反转问题:高优先级回调函数可能被低优先级回调函数阻塞。
  3. 无法显式控制回调函数的执行顺序。
  4. 缺乏对特定话题触发的内置控制。
  5. 此外,执行器在CPU和内存占用方面的开销较大。

目前,以下新特性已部分解决了上述问题:

  • rclcpp等待集(WaitSet):rclcpp的WaitSet类允许直接等待订阅、计时器、服务端、动作服务端等,无需使用执行器。通过该类可实现确定性的、用户自定义的处理序列,还能同时处理来自不同订阅的多个消息。examples_rclcpp_wait_set包提供了该用户级等待集机制的多个使用示例。
  • rclc执行器:该执行器来自C客户端库rclc(为micro-ROS开发),能让用户对回调函数的执行顺序进行细粒度控制,还支持自定义触发条件以激活回调函数。此外,它还实现了“逻辑执行时间(Logical Execution Time,LET)”语义的相关理念。

六、Topic statistics

话题统计信息(Topic Statistics)

目录

  • 概述
  • 统计信息的计算方式
  • 计算的统计信息类型
  • 行为特性
  • 与ROS 1的对比
  • 支持情况

1、概述

ROS 2为所有订阅(subscription)接收的消息提供了集成化的统计信息测量功能。允许用户收集订阅统计信息,可帮助其分析系统性能特征,或辅助诊断当前存在的问题。

该功能提供的测量指标包括“接收消息时长(received message age)”和“接收消息周期(received message period)”。对于每个测量指标,提供的统计数据包括平均值(average)、最大值(maximum)、最小值(minimum)、标准差(standard deviation)和样本数量(sample count)。这些统计数据均在一个滑动窗口内计算得出。

2、统计信息的计算方式

每一组统计数据均借助libstatistics_collector包中实现的工具,以恒定时间恒定内存开销完成计算。当订阅接收到新消息时,该消息会成为当前测量窗口中用于计算的新样本。

  • 平均值:采用滑动平均(moving average)方式计算。
  • 最大值、最小值、样本数量:每接收一个新样本就会即时更新。
  • 标准差:使用威尔福德在线算法(Welford’s online algorithm) 计算。

3、计算的统计信息类型

1. 接收消息周期(Received message period)

  • 单位:毫秒(milliseconds)
  • 计算方式:使用系统时钟(system clock)测量连续接收两条消息的时间间隔。

2. 接收消息时长(Received message age)

  • 单位:毫秒(milliseconds)
  • 计算条件:消息必须在header字段中包含时间戳(timestamp),才能计算消息从发布者发送到接收者的时长。

4、行为特性

  1. 默认状态:话题统计信息测量功能默认处于禁用状态。
  2. 启用方式:通过订阅配置选项为特定节点启用该功能后,该节点的特定订阅会同时启用“接收消息时长”和“接收消息周期”的测量。
  3. 数据发布:统计数据以statistics_msg/msg/MetricsMessage消息类型发布,发布周期(默认1秒)和发布话题(默认/statistics)均可配置。需注意,发布周期同时也是样本收集窗口的周期
  4. 无时间戳处理:若消息header中无时间戳(接收消息时长计算的必要条件),则发布空数据——即所有统计值均为NaN(非数字)。发布NaN而非完全不发布,可避免“信号缺失”问题,明确表明该测量无法完成。
  5. 首样本处理:在每个窗口中,“接收消息周期”的第一个样本无法生成有效测量值。因为该统计指标需要知道前一条消息的接收时间,所以窗口内只有后续样本才能生成测量结果。

5、与ROS 1的对比

与ROS 1的“话题统计信息”功能类似,ROS 2也会计算“消息时长”和“消息周期”,但仅从订阅端(subscription side)进行计算

目前ROS 2暂未提供ROS 1中的其他指标,例如“丢失消息数(number of dropped messages)”或“通信流量(traffic volume)”。

6、支持情况

该功能目前仅在ROS 2 Foxy版本的C++客户端库(rclcpp)中支持。关于未来的功能改进(如Python客户端库支持),可参考此处(原文“here”指向ROS 2相关开发计划页面)。

七、Overview and usage of RQt

目录

  • 概述
  • 系统设置
    • 通过deb包安装(Installing From debs)
  • RQt组件结构(RQt Components Structure)
  • RQt框架的优势(Advantage of RQt framework)

1、概述

RQt是一个图形用户界面(GUI)框架,以插件形式实现各类工具和接口。用户可在RQt中将所有现有GUI工具作为可停靠窗口运行——这些工具虽仍可通过传统的独立方式运行,但RQt能更便捷地将所有不同窗口整合到单一屏幕布局中进行管理。

通过以下命令可轻松运行任意RQt工具/插件:

$ rqt

该GUI界面会显示系统中所有可用的插件,供用户选择。用户也可在独立窗口中运行插件,例如运行RQt Python控制台(RQt Python Console):

$ ros2 run rqt_py_console rqt_py_console

用户可使用PythonC++为RQt创建自定义插件。若要查看系统中已有的RQt插件,可运行以下命令,然后查找以rqt_开头的功能包:

$ ros2 pkg list

2、系统设置

通过deb包安装(Installing From debs)

通过以下命令可通过deb包安装RQt相关组件:

$ sudo apt install ros-kilted-rqt*

3、RQt组件结构(RQt Components Structure)

RQt包含两个元功能包(metapackages):

  • rqt:核心基础架构模块(core infrastructure modules)。
  • rqt_common_plugins:常用调试工具(Commonly useful debugging tools)。

4、RQt框架的优势(Advantage of RQt framework)

相较于从零构建自定义GUI

  • 提供GUI标准化通用流程(包括启动-关闭钩子函数、恢复之前状态等)。
  • 多个控件(widget)可停靠在同一个窗口中。
  • 可轻松将现有的Qt控件转换为RQt插件。
  • 可在机器人技术问答平台(Robotics Stack Exchange,ROS社区官方问答网站)获取支持。

从系统架构角度

  • 支持多平台(基本上所有能运行Qt和ROS的平台均可)和多语言(PythonC++)。
  • 可管理的生命周期:RQt插件使用统一API,便于维护和复用。

Further Reading
ROS 2 Discourse announcement of porting to ROS 2)
RQt for ROS 1 documentation
Brief overview of RQt (from a Willow Garage intern blog post)

八、Composition

目录

  • ROS 1——节点(Nodes)与节点插件(Nodelets)的对比
  • ROS 2——统一API(Unified API)
  • 组件容器(Component Container)
  • 编写组件(Writing a Component)
  • 使用组件(Using Components)
  • 实际应用(Practical application)

1、ROS 1——节点(Nodes)与节点插件(Nodelets)的对比

在ROS 1中,代码可编写为ROS节点(ROS node)或ROS节点插件(ROS nodelet):

  • ROS 1节点会被编译为可执行文件。
  • ROS 1节点插件则被编译为共享库(shared library),之后由一个容器进程(container process)在运行时加载。

2、ROS 2——统一API(Unified API)

在ROS 2中,推荐的代码编写方式类似ROS 1的节点插件,我们称之为“组件(Component)”。这种方式便于为现有代码添加通用特性(如生命周期管理)。

ROS 1的最大缺点是节点与节点插件使用不同的API,而ROS 2避免了这一问题——两种(类似节点、类似节点插件的)实现方式共用同一套API。

:ROS 2仍支持“自定义main函数”的类节点编写风格,但对于常规场景,不推荐使用这种方式。

将进程布局(process layout)设为“部署时决策项”后,用户可根据需求选择以下两种运行方式:

  1. 多节点在独立进程中运行:优势在于进程/故障隔离,且单个节点的调试更简便。
  2. 多节点在单个进程中运行:优势在于开销更低,且可通过进程内通信(Intra Process Communication)实现更高效率的通信(详见“进程内通信”相关内容)。

此外,可通过ros2 launch工具,借助专门的启动动作(launch actions)自动执行上述部署操作。

3、组件容器(Component Container)

组件容器是一个宿主进程(host process),支持在运行时将多个组件加载到同一进程空间中,并对其进行管理。

目前,可用的通用组件容器类型如下:

  • component_container:最通用的组件容器,使用单个“单线程执行器(SingleThreadedExecutor)”执行所有组件。
  • component_container_mt:使用单个“多线程执行器(MultiThreadedExecutor)”执行组件的容器。
  • component_container_isolated:为每个组件分配专属执行器的容器,默认使用“单线程执行器(SingleThreadedExecutor)”,也可选择“多线程执行器(MultiThreadedExecutor)”。

关于执行器类型的更多信息,可参考“执行器类型(Types of Executors)”相关内容;关于每种组件容器的配置选项,可参考“组件化教程(composition tutorial)”中的“组件容器类型(Component container types)”章节。

4、编写组件(Writing a Component)

组件仅被编译为共享库,因此不存在main函数(可参考“Talker组件源码”)。组件通常是rclcpp::Node的子类。

由于组件不控制线程,因此不应在其构造函数中执行任何长时间运行或阻塞性的任务;反之,可通过计时器(timers)实现周期性通知。此外,组件还可创建发布者(publishers)、订阅者(subscriptions)、服务端(servers)和客户端(clients)。

要将类封装为组件,关键在于通过rclcpp_components包中的宏定义完成类的注册(可参考源码最后一行)。这样,当组件所在的共享库被加载到运行中的进程时,该组件就能被发现——这一注册过程相当于为组件设置了“入口点”。

此外,组件创建完成后,必须在索引(index)中注册,才能被工具链(tooling)发现。注册的CMake代码示例如下:

add_library(talker_component SHARED src/talker_component.cpp)

rclcpp_components_register_nodes(talker_component "composition::Talker")

# 若要在同一个共享库中注册多个组件,需多次调用上述注册函数
# rclcpp_components_register_nodes(talker_component "composition::Talker2")

关于组件编写的示例,可参考本教程(原文“this tutorial”指向ROS 2组件化编写教程)。

:为确保组件容器能找到目标组件,必须在“已 sourcing 对应工作空间”的终端中执行或启动组件容器。

5、使用组件(Using Components)

composition功能包中提供了多种使用组件的方法,其中最常用的三种如下:

  1. 通过服务加载:启动一个(通用容器进程),调用容器提供的load_node ROS服务。该服务会根据传入的功能包名和库名加载指定组件,并在运行中的进程内启动执行。除了通过编程方式调用该服务,也可使用命令行工具,通过传入命令行参数调用该ROS服务。
  2. 编译时集成:创建自定义可执行文件,在编译时就将多个已知节点集成其中。这种方式要求每个组件都有对应的头文件(第一种方式无此严格要求)。
  3. 通过启动文件加载:创建启动文件(launch file),使用ros2 launch工具创建容器进程,并在其中加载多个组件。

6、实际应用(Practical application)

可尝试运行“组件化演示程序(Composition demos)”,实践组件化相关功能。

9、Cross-compilation

目录

  • 概述
  • 交叉编译的工作原理
  • 替代方案

(1) 概述
Open Robotics(开放机器人联盟)为多种平台提供了预编译的ROS 2功能包,但仍有许多开发者因以下原因依赖交叉编译:

  1. 开发机器与目标系统不匹配(即开发环境和最终运行环境不一致)。
  2. 需针对特定核心架构优化构建配置(例如,为树莓派3(Raspberry Pi3)构建时,需设置-mcpu=cortex-a53 -mfpu=neon-fp-armv8编译选项)。
  3. 目标文件系统并非Open Robotics发布的预编译镜像所支持的文件系统。

(2) 交叉编译的工作原理
对于简单软件(如不依赖外部库的软件),交叉编译相对简单,只需使用交叉编译工具链替代原生工具链即可。

但以下因素会使交叉编译过程变得复杂:

  1. 目标架构兼容性:待编译的软件必须支持目标架构。与架构相关的代码(如汇编代码)必须妥善隔离,并在构建过程中根据目标架构启用。
  2. 依赖项准备:所有依赖项(如库文件)必须提前准备就绪——无论是预编译包还是交叉编译生成的包,都需在交叉编译使用这些依赖项的目标软件之前完成准备。
  3. 构建工具支持:当使用构建工具(如colcon)构建软件栈(而非独立软件)时,要求该构建工具提供一种机制,允许开发者对软件栈中每个软件所使用的底层构建系统启用交叉编译功能。

(3) 替代方案
交叉编译的一种替代方案是使用docker buildx构建多平台Docker镜像。

10、ROS 2 Security

目录

  • 概述
  • 安全隔离区(The Security Enclave)
  • 安全文件(Security Files)
    • 隔离区身份标识(Enclave Identity)
    • 隔离区权限(Enclave Permissions)
  • 私钥(Private Keys)
  • 安全环境变量(Security Environment Variables)

1、概述

ROS 2具备保护计算图内节点间通信安全的能力。与“节点发现”功能类似,安全机制通过底层ROS 2中间件实现(前提是该中间件支持相应的安全插件)。启用安全机制无需额外安装软件,但中间件要求为ROS计算图中的每个参与者(participant)配置对应的配置文件。这些文件可实现数据加密与身份认证,并为单个节点及整个ROS计算图定义安全策略。此外,ROS 2还提供一个全局“开启/关闭”开关,用于控制安全机制的启用状态。

ROS工具可为ROS应用创建权威的信任锚(trust anchor),也可使用外部证书机构(certificate authority)。

ROS 2内置的安全特性可对整个ROS计算图的通信进行管控,不仅能对ROS域参与者之间传输的数据进行加密,还能验证发送数据的参与者身份、确保所发送数据的完整性,并支持域级别的访问控制。

ROS 2的安全服务由底层的“数据分发服务(DDS)”提供——DDS是节点间通信的基础。DDS供应商提供可与ROS兼容的开源及商业版DDS实现,但根据DDS规范要求,所有供应商都必须包含《DDS安全规范》(DDS Security Specification)中规定的安全插件。ROS安全特性借助这些DDS安全插件,实现基于策略的加密、身份认证与访问控制。DDS与ROS安全机制可通过预定义的配置文件和环境变量启用。

2、安全隔离区(The Security Enclave)

安全隔离区(enclave)封装了一套用于保护ROS通信的独立策略。一个隔离区可为多个节点、整个ROS计算图,或任意组合的受保护ROS进程与设备设置安全策略。在部署阶段,安全隔离区可灵活映射到进程、用户或设备。对于复杂系统及通信优化场景,调整这种默认映射行为十分重要。更多细节可参考《ROS 2安全隔离区设计文档》(ROS 2 Security Enclaves design document)。

3、安全文件(Security Files)

根据DDS规范,一个ROS 2安全隔离区需通过6个文件创建。其中3个文件定义隔离区的“身份标识”,另外3个文件定义授予隔离区的“权限”。这6个文件均存放在同一个目录中;若启动节点时未指定有效的隔离区路径,节点将使用默认根级别隔离区中的文件。

隔离区身份标识(Enclave Identity)

  • 身份证书机构文件(identity_ca.cert.pem:作为信任锚,用于识别ROS域中的参与者。
  • 隔离区身份证书文件(cert.pem:每个隔离区都持有唯一的身份证书,该证书由身份证书机构(identity CA)签名。
  • 隔离区私钥文件(key.pem:与cert.pem证书关联的私钥。

当某一参与者向域内其他成员出示cert.pem证书时,其他成员可通过自身持有的身份证书(identity_ca.cert.pem)验证该参与者的身份。这种有效的证书交换机制,能让隔离区与其他参与者建立安全的可信通信。需注意,隔离区不会共享key.pem私钥,仅将其用于数据解密与消息签名。

隔离区权限(Enclave Permissions)

  • 权限证书机构文件(permissions_ca.cert.pem:作为信任锚,用于向安全隔离区授予权限。
  • 域级策略文件(governance.p7s:由权限证书机构签名的XML文档,定义整个ROS域的全局保护策略。
  • 隔离区权限文件(permissions.p7s:由权限证书机构签名的XML文档,明确当前隔离区的具体权限。

域内成员会使用自身持有的权限证书(permissions_ca.cert.pem)验证上述两个签名文件的有效性,并根据验证结果授予相应的访问权限。

尽管身份证书机构与权限证书机构支持两种独立的工作流程(分别对应身份认证与权限管控),但实际应用中,常使用同一个证书同时作为身份证书机构和权限证书机构。

4、私钥(Private Keys)

身份证书与权限证书均有对应的私钥文件。新增隔离区到ROS域的操作流程如下:

  1. 使用身份证书的私钥,对新增隔离区的“证书签名请求(CSR,Certificate Signing Request)”进行签名,完成隔离区身份注册。
  2. 使用权限证书的私钥,对新增隔离区的权限XML文档进行签名,为隔离区授予相应权限。

5、安全环境变量(Security Environment Variables)

1. ROS_SECURITY_ENABLE

作为ROS 2安全特性的全局“开启/关闭”开关。默认情况下,安全机制处于关闭状态——即便存在正确的安全文件,安全特性也不会启用。若需启用ROS 2安全机制,需将该环境变量设置为true(区分大小写)。

2. ROS_SECURITY_STRATEGY

在安全机制已启用的前提下,该变量定义ROS域参与者启动时遇到问题的处理策略。安全特性依赖证书及签名有效的配置文件,但默认情况下,配置不当的参与者仍能成功启动,只是不会启用安全特性。若需强制要求所有参与者遵守安全配置(配置不兼容的隔离区将启动失败),需将该环境变量设置为Enforce(区分大小写)。

更多与安全相关的环境变量,可参考《ROS 2 DDS安全集成设计文档》(ROS 2 DDS-Security Integration design document)。这些变量通常用于辅助ROS管理隔离区及定位安全文件。

11、Tf2

变换库(Tf2)

目录

  • 概述
  • Tf2的特性
  • 发布变换(Publishing transforms)
    • 位置(Position)
    • 速度(Velocity)
  • 教程(Tutorials)
  • 相关论文(Paper)

概述

Tf2是一款变换库,能帮助用户实时跟踪多个坐标系(coordinate frame)的变化。Tf2会以时间缓冲的树形结构维护坐标系之间的关系,支持用户在任意时间点将点、向量等数据在任意两个坐标系之间进行变换。

Tf2的特性

机器人系统通常包含多个随时间变化的3D坐标系,例如世界坐标系(world frame)、底座坐标系(base frame)、夹爪坐标系(gripper frame)、头部坐标系(head frame)等。Tf2可实时跟踪所有这些坐标系的变化,并支持用户查询以下类型的问题:

  • 5秒前,头部坐标系相对于世界坐标系的位置在哪里?
  • 夹爪中物体的位姿(pose)相对于底座坐标系是怎样的?
  • 当前底座坐标系在地图坐标系(map frame)中的位姿是什么?

Tf2可在分布式系统中运行——这意味着机器人所有坐标系的信息,对系统内任意计算机上的所有ROS 2组件都是可访问的。在分布式系统中,Tf2有两种工作模式:要么让系统中的每个组件都构建自己的变换信息数据库,要么通过一个中心节点收集并存储所有变换信息。

(坐标系示例)

          地球(Earth)
       ↙      ↓      ↘
    汽车A(Car A)  汽车B(Car B)  卫星C(Satellite C)
                     ↘
                      月球D(Moon D)

发布变换(Publishing transforms)

发布变换时,我们通常将其理解为“从一个坐标系到另一个坐标系的变换”。需要注意“变换坐标系中的数据”与“变换坐标系本身”在语义上的区别:这两种操作的结果是直接互逆的。

通过geometry_msgs/msg/Transform消息发布的变换,描述的是“坐标系本身的变换”。调试发布的变换时需牢记:根据遍历变换树的方向不同,查询到的变换结果与发布的变换结果是互逆的。

变换公式表示为:
B T d a t a = ( B T f r a m e ) − 1 ^B T_{data} = (^B T_{frame})^{-1} BTdata=(BTframe)1

Tf库会根据你遍历变换树的方向,自动处理这些元素的逆运算。在本文档后续内容中,我们将统一使用 T d a t a T_{data} Tdata表示变换(省略“data”的具体指代)。

位置(Position)

假设汽车A(Car A)的驾驶员观察到某个物体,地面上的人想知道该物体相对于自己位置的坐标——此时需将“观察到的物体坐标”从源坐标系(汽车A的坐标系) 变换到目标坐标系(地面人的坐标系)

变换公式表示为:
E T A ∗ P A O b s = P E O b s ^E T_A * P_{AObs} = P_{EObs} ETAPAObs=PEObs

(其中, E T A ^E T_A ETA表示从汽车A坐标系到地球坐标系(Earth)的变换, P A O b s P_{AObs} PAObs是物体在汽车A坐标系中的坐标, P E O b s P_{EObs} PEObs是物体在地球坐标系中的坐标)

若汽车B(Car B)中的人也想知道该物体的坐标,可通过计算总变换得到:
B T E ∗ E T A ∗ P A O b s = B T A ∗ P A O b s = P B O b s ^B T_E * ^E T_A * P_{AObs} = ^B T_A * P_{AObs} = P_{BObs} BTEETAPAObs=BTAPAObs=PBObs

(其中, B T E ^B T_E BTE表示从地球坐标系到汽车B坐标系的变换, P B O b s P_{BObs} PBObs是物体在汽车B坐标系中的坐标)

这正是lookupTransform函数的核心功能——在该函数中,A对应源坐标系ID(source frame_id),B对应目标坐标系ID(target frame_id)。

建议尽可能使用transform<T>(target_frame, ...)系列方法进行变换:这些方法会自动从数据类型中读取源坐标系ID,并将目标坐标系ID写入变换结果的数据类型中,内部会自动处理所有数学运算。

P是带时间戳的数据类型(Stamped datatype),则其frame_id属性即为源坐标系A

举个例子:若根坐标系A在坐标系B下方1米处,那么“从AB的变换”结果为正值。但将数据从坐标系B变换到坐标系A时,需使用该值的逆——因为切换到更低的参考坐标系时,高度值会增加;反之,将数据从坐标系A变换到坐标系B时,由于参考坐标系变高,高度值会减少。

对应的变换公式为:
B T A = ( B T f A ) − 1 ^B T_A = (^B T_{fA})^{-1} BTA=(BTfA)1

速度(Velocity)

表示“速度(Velocity)”需包含三部分信息:
V o b s e r v i n g _ f r a m e m o v i n g _ f r a m e − r e f e r e n c e _ f r a m e V_{observing\_frame}^{moving\_frame - reference\_frame} Vobserving_framemoving_framereference_frame

该速度描述的是“运动坐标系(moving frame)”相对于“参考坐标系(reference frame)”的运动速度,且速度值在“观测坐标系(observing frame)”下表示。

举个例子:

  • 汽车A中的驾驶员报告“自己正以1m/s的速度向前行驶(在汽车A坐标系下观测),且速度相对于地球(Earth)”,可表示为:
    V A A − E = ( 1 , 0 , 0 ) V_A^{A - E} = (1, 0, 0) VAAE=(1,0,0)
    (其中,观测坐标系为A,运动坐标系为A,参考坐标系为E)

  • 若从地球坐标系观测该速度(假设汽车朝东行驶,且地球坐标系采用“北-东-下(NED)”坐标系),则速度可表示为:
    V E A − E = ( 0 , 1 , 0 ) V_E^{A - E} = (0, 1, 0) VEAE=(0,1,0)

通过变换可证明这两个速度本质上是相同的,公式如下:
E T A ∗ V A A − E = V E A − E ^E T_A * V_A^{A - E} = V_E^{A - E} ETAVAAE=VEAE

速度具有以下特性:

  1. 若多个速度在同一坐标系(如下例中的“观测坐标系Obs”)下表示,则可进行加减运算:
    V O b s A − C = V O b s A − B + V O b s D − C V_{Obs}^{A - C} = V_{Obs}^{A - B} + V_{Obs}^{D - C} VObsAC=VObsAB+VObsDC
  2. 速度可通过取逆“反向”:
    V O b s A − C = − ( V O b s C − A ) V_{Obs}^{A - C} = -(V_{Obs}^{C - A}) VObsAC=(VObsCA)
  3. 若要比较两个速度,必须先将它们变换到同一个观测坐标系下。

教程(Tutorials)

我们提供了一系列循序渐进的Tf2使用教程,可从“Tf2入门教程”(introduction to tf2 tutorial)开始学习。如需查看所有Tf2及Tf2相关教程的完整列表,可访问教程页面(tutorials page)。

用户使用Tf2主要涉及两项核心任务:监听变换(listening for transforms)和发布变换(broadcasting transforms)。

1. 监听变换

若需使用Tf2进行坐标系间的变换,节点需先“监听”变换——即接收并缓存系统中所有已发布的坐标系信息,再查询特定坐标系之间的变换。如需了解更多,可参考“编写监听节点”教程(Python版 / C++版)。

2. 发布变换

若需扩展机器人的功能,需“发布”变换——即向系统中其他组件发送坐标系的相对位姿信息。一个系统中可存在多个发布者,每个发布者负责提供机器人不同部件的坐标系信息。如需了解更多,可参考“编写发布节点”教程(Python版 / C++版)。

3. 发布静态变换

此外,Tf2还支持发布“静态变换”(即不随时间变化的变换)。这种方式主要能节省存储和查询时间,同时降低发布开销。需注意:静态变换仅发布一次,且默认认为不会变化,因此不会存储历史数据。如需在Tf2树中定义静态变换,可参考“编写静态发布节点”教程(Python版 / C++版)。

4. 其他教程

还可通过“添加坐标系”教程(Python版 / C++版),学习如何在Tf2树中添加固定坐标系(fixed frames)和动态坐标系(dynamic frames)。

完成基础教程后,可进一步学习Tf2与时间相关的内容:

  • 《Tf2与时间》教程(C++版):讲解Tf2与时间相关的基本原理。
  • 《Tf2与时间(进阶)》教程(C++版):讲解如何使用Tf2实现“时间回溯”(time traveling)功能。

相关论文(Paper)

关于Tf2的论文已在2013年TePRA(IEEE International Conference on Technologies for Practical Robot Applications)会议上发表,论文标题为《tf: The transform library》。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ray.so

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值