内部 ROS 2 接口

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

内部 API 架构概述

主要有两个内部接口:

  • ROS 中间件接口(“rmw” API

  • ROS 客户端库接口(“rcl” API

“rmw” API 是 ROS 2 软件堆栈和底层中间件实现之间的接口。

用于 ROS 2 的底层中间件是 DDS 或 RTPS 实现,负责发现、发布和订阅机制、服务的请求-回复机制以及消息类型的序列化。

“rcl” API 是一个稍高级别的 API,用于实现 |客户端库|,不直接接触中间件实现,而是通过 ROS 中间件接口(“rmw” API)抽象来实现。

alt:

ros2 软件堆栈

如图所示,这些 |API|被堆叠在一起,使得典型的 ROS 用户将使用 |客户端库| API,例如 rclcpp,来实现他们的代码(可执行文件或库)。

|客户端库|的实现,例如 rclcpp,使用 rcl 接口,该接口提供对 ROS 图形和图形事件的访问。

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 文件)到用户和系统用于执行类型特定功能的类型特定代码的路径:

ros2 idl static type support stack

Figure: flow chart of “static” type support generation, from rosidl files to user facing code.

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

另外,.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,但由于类型支持代码中的抽象,仍未向用户公开。

动态类型支持

实现类型支持的另一种方法是使用通用函数来处理诸如发布到主题之类的事情,而不是为每种消息类型生成一个版本的函数。

为了实现这一点,这个通用函数需要一些关于正在发布的消息类型的元信息,例如字段名称和类型的列表,它们按它们在消息类型中出现的顺序排列。

然后,要发布消息,请调用通用发布函数并传递要发布的消息以及包含有关消息类型的必要元数据的结构。

这称为“动态”类型支持,而不是“静态”类型支持,后者需要为每种类型生成一个版本的函数。

ros2 idl dynamic type support stack

Figure: flow chart of “dynamic” type support generation, from rosidl files to user facing code.

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

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

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

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

“rcl”存储库

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

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

有关 rcl API 的完整定义,请参阅 the rcl docs.

rmw 存储库

ROS 中间件接口(“rmw” API)是构建 ROS 所需的最小原始中间件功能集。

不同中间件实现的提供商必须实现此接口才能支持整个 ROS 堆栈。

目前,所有中间件实现都是针对不同的 DDS 供应商的。

rmw API 位于 ros2/rmw 存储库。 rmw |包| 包含定义接口的 C 标头,该接口的实现由不同 DDS 供应商的 rmw 实现的各种 |包| 提供。

有关 rmw API 的定义,请参阅 the rmw docs.

rosidl 存储库

rosidl API 包含一些与消息相关的静态函数和类型,以及不同语言的消息应生成哪些代码的定义。

API 中指定的生成消息代码将特定于语言,但可能会或可能不会重用其他语言的生成代码。

API 中指定的生成消息代码包含消息数据结构、构造函数、析构函数等。

API 还将实现一种获取消息类型的类型支持结构的方法,该方法用于发布或订阅该消息类型的主题。

有多个存储库在 rosidl API 和实现中发挥作用。

rosidl 存储库位于 GitHubros2/rosidl, 定义消息 IDL 语法,即 .msg 文件、.srv 文件等的语法,并包含用于解析文件、提供 CMake 基础结构以从消息生成代码、生成与实现无关的代码(标头和源文件)以及建立默认生成器集的 packages。 存储库包含以下 packages

  • rosidl_cmake: provides CMake functions and modules for generating code from rosidl files, e.g. .msg files, .srv files, etc.

  • rosidl_default_generators: defines the list of default generators which ensures that they are installed as dependencies, but other injected generators can also be used.

  • rosidl_generator_c: provides tools to generate C header files (.h) for rosidl files.

  • rosidl_generator_cpp: provides tools to generate C++ header files (.hpp) for rosidl files.

  • rosidl_generator_py: provides tools to generate Python modules for rosidl files.

  • rosidl_parser: provides Python API for parsing rosidl files.

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

除了上述用于解析和生成 rosidl 文件头的 packages 之外,rosidl 存储库还包含与文件中定义的消息类型的“类型支持”有关的 packages

类型支持是指解释和操作特定类型的 ROS 消息实例所表示的信息的能力(例如,发布消息)。 类型支持可以通过编译时生成的代码提供,也可以基于 rosidl 文件的内容(例如 .msg.srv 文件)和收到的数据通过自省数据以编程方式完成。 对于后者,类型支持是通过运行时解释消息来完成的,ROS 2 生成的消息代码可能与 rmw 实现无关。 通过数据自省提供这种类型支持的软件包有:

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

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

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

有关 rosidl |API|(静态和生成)的具体内容的更多信息,请参阅此页面:

rcutils 存储库

ROS 2 C 实用程序 (rcutils) 是一个 C API,由整个 ROS 2 代码库中使用的宏、函数和数据结构组成。

它们主要用于错误处理、命令行参数解析和日志记录,这些并不特定于客户端或中间件层,并且可以由两者共享。

rcutils API 和实现位于 ros2/rcutils GitHub 上的存储库包含作为 C 标头的接口。

有关 rcutils API 的完整定义,请参阅 the rcutils docs.