跳转至

ROS 2 内部接口

目录

ROS 内部接口是公共的 C API,旨在供那些创建客户端库或添加新底层中间件的开发人员使用,而不是供典型的 ROS 用户使用。ROS 客户端库提供了大多数 ROS 用户所熟悉的面向用户的 API,并且可能有多种编程语言版本。

内部 API 架构概览

主要有两个内部接口:

  • ROS 中间件接口 (rmw API)
  • ROS 客户端库接口 (rcl API)

rmw API 是 ROS 2 软件栈与底层中间件实现之间的接口。ROS 2 使用的底层中间件是 DDS 或 RTPS 实现,负责发现、发布和订阅机制、服务的请求-应答机制以及消息类型的序列化。

rcl API 是一个稍高层级的 API,用于实现客户端库,它不直接接触中间件实现,而是通过 ROS 中间件接口 (rmw API) 抽象层来进行操作。

如图所示,这些 API 是层叠的,因此典型的 ROS 用户将使用客户端库 API(例如 rclcpp)来实现他们的代码(可执行文件或库)。客户端库(例如 rclcpp)的实现使用 rcl 接口,该接口提供对 ROS 图(ROS graph)和图事件的访问。rcl 实现进而使用 rmw API 来访问 ROS 图。rcl 实现的目的是为更复杂的 ROS 概念和实用工具提供通用的实现,以便各种客户端库使用,同时保持与所使用的底层中间件无关。rmw 接口的目的是捕获支持 ROS 客户端库所需的绝对最小中间件功能。最后,rmw API 的实现由特定于中间件实现的(例如 rmw_fastrtps_cpp)提供,该库是针对特定供应商的 DDS 接口和类型编译的。

在上图中,还有一个标记为 ros_to_dds 的框,此框的目的是代表一类可能的包,这些包允许用户使用 ROS 等效项访问 DDS 供应商特定的对象和设置。此抽象接口的目标之一是将 ROS 用户空间代码与正在使用的中间件完全隔离,以便更改 DDS 供应商甚至中间件技术对用户代码的影响最小。然而,我们认识到,尽管可能会有后果,但有时深入实现并手动调整设置是有用的。通过要求使用这些包之一来访问底层 DDS 供应商的对象,我们可以避免在普通接口中暴露特定于供应商的符号和头文件。通过检查包的依赖关系以查看是否使用了这些 ros_to_dds 包之一,也可以轻松查看哪些代码可能违反了供应商可移植性。

类型特定接口

在此过程中,API 的某些部分必然特定于正在交换的消息类型(例如,发布消息或订阅主题),因此需要为每种消息类型生成代码。下图展示了从用户定义的 rosidl 文件(例如 .msg 文件)到用户和系统用于执行类型特定功能的类型特定代码的路径:

图:“静态”类型支持生成的流程图,从 rosidl 文件到面向用户的代码。

图的右侧显示了 .msg 文件如何直接传递给特定于语言的代码生成器,例如 rosidl_generator_cpprosidl_generator_py。这些生成器负责创建用户将包含(或导入)的代码,并将其用作 .msg 文件中定义的消息的内存表示。例如,考虑消息 std_msgs/String,用户可能通过语句 #include <std_msgs/msg/string.hpp> 在 C++ 中使用此文件,或者通过语句 from std_msgs.msg import String 在 Python 中使用。这些语句之所以有效,是因为这些特定于语言(但与中间件无关)的生成器包生成了文件。

另外,.msg 文件用于为每种类型生成类型支持代码。在这种情况下,类型支持意味着:特定于给定类型的元数据或函数,系统使用它们来为给定类型执行特定任务。给定消息的类型支持可能包括诸如消息中每个字段的名称和类型列表之类的内容。它还可能包含对可以为该类型执行特定任务(例如发布消息)的代码的引用。

静态类型支持

当类型支持引用代码来为特定消息类型执行特定功能时,该代码有时需要执行特定于中间件的工作。例如,考虑特定于类型的发布函数,当使用“供应商 A”时,该函数将需要调用“供应商 A”的一些 API,但是当使用“供应商 B”时,它将需要调用“供应商 B”的 API。为了允许特定于中间件供应商的代码,用户定义的 .msg 文件可能会导致生成特定于供应商的代码。通过类型支持抽象,此特定于供应商的代码仍然对用户隐藏,这类似于“私有实现”(或 Pimpl)模式的工作方式。

基于 DDS 的静态类型支持

对于基于 DDS 的中间件供应商,特别是那些基于 OMG IDL 文件(.idl 文件)生成代码的供应商,用户定义的 rosidl 文件(.msg 文件)将转换为等效的 OMG IDL 文件(.idl 文件)。从这些 OMG IDL 文件中,创建特定于供应商的代码,然后在特定于类型的函数中使用这些代码,这些函数由给定类型的类型支持所引用。上面的图表在左侧显示了这一点,其中 .msg 文件由 rosidl_dds 包使用以生成 .idl 文件,然后将这些 .idl 文件提供给特定于语言和特定于 DDS 供应商的类型支持生成包。

例如,考虑 Fast DDS 实现,它有一个名为 rosidl_typesupport_fastrtps_cpp 的包。此包负责生成代码来处理诸如将 C++ 消息对象转换为通过网络写入的序列化八位字节缓冲区之类的事情。此代码虽然特定于 Fast DDS,但由于类型支持代码中的抽象,仍然不会暴露给用户。

动态类型支持

实现类型支持的另一种方法是拥有用于诸如发布到主题之类的通用函数,而不是为每种消息类型生成一个版本的函数。为了实现这一点,此通用函数需要有关正在发布的消息类型的一些元信息,例如按消息类型中出现顺序列出的字段名称和类型列表。然后,要发布消息,您调用一个通用发布函数,并传递要发布的消息以及包含有关消息类型的必要元数据的结构。这被称为“动态”类型支持,以区别于需要为每种类型生成函数版本的“静态”类型支持。

图:“动态”类型支持生成的流程图,从 rosidl 文件到面向用户的代码。

上图显示了从用户定义的 rosidl 文件到生成的面向用户代码的流程。它与静态类型支持的图表非常相似,区别仅在于生成类型支持的方式,如由图表左侧所示。在动态类型支持中,.msg 文件直接转换为面向用户的代码。

此代码也是与中间件无关的,因为它仅包含有关消息的元信息。实际执行工作的函数(例如发布到主题)对于消息类型是通用的,并将对特定于中间件的 API 进行任何必要的调用。请注意,此方法不是由特定于 DDS 供应商的包提供类型支持代码(静态类型支持即是如此),而是每种语言都有与中间件无关的包,例如 rosidl_typesupport_introspection_crosidl_typesupport_introspection_cpp。包名称中的 introspection(自省/内省)部分是指使用生成的消息类型元数据自省任何消息实例的能力。这是允许像“发布到主题”这样的函数的通用实现的基本能力。

这种方法的优点是所有生成的代码都与中间件无关,这意味着它可以用于不同的中间件实现,只要它们允许动态类型支持。它还可以减少生成的代码量,从而减少编译时间和代码大小。

但是,动态类型支持要求底层中间件支持类似形式的动态类型支持。在 DDS 的情况下,DDS-XTypes 标准允许使用元信息而不是生成的代码发布消息。底层中间件需要 DDS-XTypes 或类似的东西才能支持动态类型支持。此外,这种类型支持方法通常比静态类型支持替代方案慢。静态类型支持中的特定于类型的生成代码可以编写得更有效率,因为它不需要迭代消息类型的元数据来执行序列化等操作。

rcl 仓库

ROS 客户端库接口 (rcl API) 可供客户端库(例如 rclcrclcpprclpy 等)使用,以避免重复逻辑和功能。通过重用 rcl API,客户端库可以更小并且彼此更加一致。客户端库的某些部分被特意排除在 rcl API 之外,因为应该使用语言惯用的方法来实现系统的这些部分。这方面的一个很好的例子是执行模型,rcl 完全不涉及它。相反,客户端库应该提供一种语言惯用的解决方案,如 C 中的 pthreads,C++11 中的 std::thread 和 Python 中的 threading.Thread。通常,rcl 接口提供的函数不特定于语言模式,也不特定于特定的消息类型。

rcl API 位于 GitHub 上的 ros2/rcl 仓库中,并包含作为 C 头文件的接口。rcl C 实现由同一仓库中的 rcl 包提供。此实现通过使用 rmwrosidl API 来避免与中间件直接接触。

有关 rcl API 的完整定义,请参阅 rcl 文档

rmw 仓库

ROS 中间件接口 (rmw API) 是在其之上构建 ROS 所需的最小原始中间件功能集。不同中间件实现的提供者必须实现此接口,以支持其之上的整个 ROS 堆栈。目前,大多数中间件实现都是针对不同的 DDS 供应商的。

rmw API 位于 ros2/rmw 仓库中。rmw 包包含定义接口的 C 头文件,其实现由针对不同 DDS 供应商的各种 rmw 实现提供。

有关 rmw API 的定义,请参阅 rmw 文档

有关 ROS 2 如何与不同中间件实现集成的更实用的深入概述,请参阅中间件实现教程

rosidl 仓库

rosidl API 由一些与消息相关的静态函数和类型以及关于不同语言的消息应生成什么代码的定义组成。API 中指定的生成消息代码将是特定于语言的,但也可能会或可能不会重用其他语言生成的代码。API 中指定的生成消息代码包含诸如消息数据结构、用于构造、销毁的函数等内容。API 还将实现一种获取消息类型的类型支持结构的方法,该结构在发布或订阅该消息类型的主题时使用。

有几个仓库在 rosidl API 和实现中发挥作用。

rosidl 仓库位于 GitHub 的 ros2/rosidl,它定义了消息 IDL 语法,即 .msg 文件、.srv 文件等的语法,并包含用于解析文件、提供 CMake 基础设施以从消息生成代码、生成与实现无关的代码(头文件和源文件)以及建立默认生成器集的。该仓库包含这些

  • rosidl_cmake:提供 CMake 函数和模块,用于从 rosidl 文件(例如 .msg 文件、.srv 文件等)生成代码。
  • rosidl_default_generators:定义默认生成器列表,确保它们作为依赖项安装,但也可是使用其他注入的生成器。
  • rosidl_generator_c:提供为 rosidl 文件生成 C 头文件 (.h) 的工具。
  • rosidl_generator_cpp:提供为 rosidl 文件生成 C++ 头文件 (.hpp) 的工具。
  • rosidl_generator_py:提供为 rosidl 文件生成 Python 模块的工具。
  • rosidl_parser:提供用于解析 rosidl 文件的 Python API。

其他语言的生成器(例如 rosidl_generator_java)托管在外部(在不同的仓库中),但会使用与上述生成器相同的机制将自己“注册”为 rosidl 生成器。

除了上述用于解析和生成 rosidl 文件头文件的之外,rosidl 仓库还包含与文件中定义的消息类型的“类型支持”有关的。类型支持是指解释和操作特定类型的 ROS 消息实例所表示的信息的能力(例如,发布消息)。类型支持既可以由编译时生成的代码提供,也可以根据 rosidl 文件(例如 .msg.srv 文件)的内容和接收到的数据,通过自省数据以编程方式完成。在后者的情况下,即通过消息的运行时解释来完成类型支持,ROS 2 生成的消息代码可以与 rmw 实现无关。通过数据自省提供这种类型支持的包有:

  • rosidl_typesupport_introspection_c:提供用于生成 C 代码以支持 rosidl 消息数据类型的工具。
  • rosidl_typesupport_introspection_cpp:提供用于生成 C++ 代码以支持 rosidl 消息数据类型的工具。

在需要在编译时生成类型支持而不是以编程方式生成的情况下,将需要使用特定于 rmw 实现的包。这是因为通常特定的 rmw 实现将需要以特定于 DDS 供应商的方式存储和操作数据,以便 DDS 实现能够利用它。有关更多详细信息,请参阅上面的类型特定接口部分。

有关 rosidl API(静态和生成的)中确切内容的更多信息,请参阅此页面。

rcutils 仓库

ROS 2 C Utilities (rcutils) 是一个 C API,由整个 ROS 2 代码库中使用的宏、函数和数据结构组成。这些主要用于错误处理、命令行参数解析和日志记录,它们不特定于客户端或中间件层,可以由两者共享。

rcutils API 和实现位于 GitHub 上的 ros2/rcutils 仓库中,其中包含作为 C 头文件的接口。

有关 rcutils API 的完整定义,请参阅 rcutils 文档