关于内部ROS 2接口
内部ROS接口是公共的C APIs 供正在创建 |客户端库| 或添加新的底层中间件的开发人员使用,但不适用于典型的ROS用户。ROS |客户端库| 提供大多数ROS用户熟悉的面向用户 APIs ,并且可能来自多种编程语言。 [Alyssa@9842]
内部API架构概述
有两个主要的内部接口: [Alyssa@9844]
ROS中间件接口 (
rmw
API) [Alyssa@9845]ROS客户端库接口 (
rcl
API) [Alyssa@9846]
rmw
API 是ROS 2软件堆栈和底层中间件实现之间的接口。用于ROS 2的底层中间件是DDS或RTPS实现,负责发现、发布和订阅机制、服务的请求-回复机制以及消息类型的序列化。 [Alyssa@9847]
rcl
API 是一个稍上层的 API,用于实现 |客户端库| 并且不直接接触中间件实现,而是通过ROS中间件接口 ( rmw
API) 抽象来做到这一点。 [Alyssa@9848]
如图所示,这些 API 被堆叠,使得典型的ROS用户将使用 |客户端库| API,例如 rclcpp
,实现他们的代码 (可执行文件或库文件)。|客户端库| 的实现,例如 rclcpp
,使用 rcl
接口,该接口提供对ROS图和图事件的访问。 rcl
实施又使用 rmw
API 访问ROS图。 rcl
实现的目的是为可能被各种 |客户端库| 使用的更复杂的ROS概念和实用工具提供一个通用的实现,同时与正在使用的底层中间件无关。 rmw
接口的目的是获取支持ROS客户端库所需的绝对最小中间件功能。最后, rmw
API 的实现由中间件实现特定 |包|,例如 rmw_fastrtps_cpp
,其库是根据供应商特定的DDS接口和类型编译的。 [Alyssa@9850]
在上图中还有一个标有 ros_to_dds
的方框,此框的目的是表示一种可能的包的类别,这种类型的包允许用户使用ROS等效项访问DDS供应商特定的对象和设置。该抽象接口的目标之一是将ROS用户空间代码与正在使用的中间件完全隔离,从而使不断变化的DDS供应商甚至中间件技术对用户代码的影响最小。然而,我们认识到,有时进入底层实现并手动调整设置是有用的,尽管可能会有不良后果。通过要求使用这些包中的一个来访问底层DDS供应商的对象,我们可以避免在正常接口中暴露供应商特定的符号和头文件。通过检查包的依赖关系以查看是否使用了这些 ros_to_dds
包之一,也可以很容易地看到哪些代码可能违反了供应商的可移植性。 [Alyssa@9851]
特定类型接口
一直以来,APIs 的某些部分必然特定于要交换的消息类型,例如发布消息或订阅话题,因此,需要为每种消息类型生成代码。下图展示了从用户定义的 rosidl
文件 (例如 .msg
文件) 到用户和系统用于执行特定类型功能的特定类型代码的路径: [Alyssa@9853]
图表的右侧显示了 .msg
文件是如何直接传递给特定语言代码生成器的,例如 rosidl_generator_cpp
或 rosidl_generator_py
。这些生成器负责创建用户将包括 (或导入) 的代码,并将其用作 .msg
文件中定义的消息的内存表示。例如,考虑消息 std_msgs/String
,用户可以在C++ 中通过语句 #include <std_msgs/msg/string.hpp>
,或者在Python中通过语句 from std_msgs.msg import String
来使用此文件。这些语句之所以有效,就是因为这些语言特定 (但与中间件无关) 生成器包生成的文件。 [Alyssa@9856]
另外, .msg
文件用于为每种类型生成类型支持代码。在这种情况下,类型支持是指: 特定于给定类型以及由系统用于执行给定类型的特定任务的元数据或函数。对给定消息的类型支持可能包括消息中每个字段的名称和类型列表。它还可能包含对可以为该类型执行特定任务的代码的引用,例如发布消息。 [Alyssa@9857]
静态类型支持
当类型支持引用代码来执行特定消息类型的特定功能时,该代码有时需要执行中间件特定的工作。例如,考虑特定类型的发布函数,当使用 "vendor A" 时,该函数需要调用 "vendor A" 的 API,但是当使用 "vendor B" 时,它需要调用 "vendor B" 的 API。为了允许中间件供应商特定代码,用户定义的 .msg
文件可能导致供应商特定代码的生成。通过类型支持抽象,这个供应商特定的代码仍然对用户隐藏,这类似于 "私有实现" (或Pimpl) 模式的工作方式。 [Alyssa@9859]
DDS的静态类型支持 [Alyssa@9860]
对于基于DDS的中间件供应商,确切的说是那些基于OMG IDL文件 ( .idl
文件) 生成代码的,用户定义的 rosidl
文件 ( .msg
文件) 被转换成等效的OMG IDL文件 ( .idl
文件)。从这些OMG IDL文件中,创建供应商特定的代码,然后在类型特定的函数中使用,这些函数由给定类型的类型支持引用。上面的图表在左手边展示了这一点,在这里 .msg
的文件通过 rosidl_dds
包来产生 .idl
的文件,然后这些 .idl
文件被提供给特定语言和特定DDS供应商的类型支持生成包。 [Alyssa@9861]
例如,考虑Connext DDS实现,它有一个被称为 rosidl_typesupport_connext_cpp
的包。这个包负责使用 rosidl_dds
包生成的 .idl
文件生成代码来处理诸如发布到给定消息类型的c ++ 版本的话题之类的事情。同样,该代码虽然特定于Connext,但由于类型支持中的抽象,仍然没有向用户公开。 [Alyssa@9862]
动态类型支持
实现类型支持的另一种方法是具有用于发布到话题之类的通用函数,而不是为每种消息类型生成函数的版本。为了实现这一点,这个通用函数需要一些关于正在发布的消息类型的元信息,诸如字段名称和类型在消息类型中显示顺序的列表之类的内容。然后,要发布消息,您可以调用通用发布函数,并传递要发布的消息以及包含有关消息类型的必要元数据的结构。这被称为 "动态" 型支持,与 "静态" 型支持相反, "静态" 型支持需要每种类型的函数的生成版本。 [Alyssa@9864]
上图显示了从用户定义的 rosidl
文件到生成的面向用户的代码的流程。它与静态类型支持的图非常相似,并且仅在图的左侧表示的类型支持的生成方式上有所不同。在动态类型支持中, .msg
文件直接转换为面向用户的代码。 [待校准@9867]
此代码也与中间件无关,因为它仅包含有关消息的元信息。实际完成工作的函数,例如发布到一个话题,对于消息类型是通用的,并且将对中间件特定的 APIs 进行任何必要的调用。请注意,此方法不是为dds供应商提供类型支持代码的特定包 (静态类型支持就是这种情况),而是为每种语言提供中间件不可知的包,例如 rosidl_typesupport_introspection_c
和 rosidl_typesupport_introspection_cpp
。包名称的 introspection
部分指的是利用消息类型生成的元数据对任何消息实例进行自检的能力。这是允许像 "发布到一个话题" 这样的功能通用实现的基本能力。 [Alyssa@9868]
这种方法的优点是所有生成的代码都与中间件无关,这意味着只要它们允许动态类型支持,就可以将其复用于不同的中间件实现。它还会导致生成的代码更少,从而减少编译时间和代码大小。 [Alyssa@9869]
但是,动态类型支持要求底层中间件支持类似形式的动态类型支持。对于DDS,DDS-XTypes标准允许使用元信息而不是生成的代码发布消息。为了支持动态类型支持,底层中间件中需要DDS-XTypes或类似的东西。此外,这种类型支持的方法通常比静态类型支持的替代方法慢。静态类型支持中的特定类型生成的代码,可以编写得更有效率,因为它不需要迭代消息类型的元数据来执行诸如序列化之类的操作。 [Alyssa@9870]
rcl
仓库 [Alyssa@9871]
ROS客户端库接口 ( rcl
API) 可被 |客户端库| (例如 rclc
、 rclcpp
、 rclpy
等)使用,以避免重复逻辑和特征。通过复用 rcl
API,客户端库可以更小,彼此更一致。客户端库的某些部分被故意排除在 rcl
API 之外,因为应该使用语言惯用方法来实现系统的这些部分。一个很好的例子就是执行模型, rcl
根本没有涉及这个问题。相反,客户端库应该提供一种语言惯用解决方案,如C中的 pthreads
、C++11中的 std::thread
和Python中的 threading.Thread
。通常, rcl
接口提供的功能不是特定于语言模式的,也不是特定于特定消息类型的。 [Alyssa@9872]
rcl
API 位于 GitHub 上的 ros2/rcl 仓库中,并且包含C头文件接口。 rcl
的C实现由 rcl
|包| 在同一个仓库提供。这种实现避免了与中间件的直接接触,而是使用了 rmw
和 rosidl
APIs。 [Alyssa@9873]
有关 rcl
API 的完整定义,请参阅其 API 文档 : [Alyssa@9874]
rmw
仓库 [Alyssa@9875]
ROS中间件接口 ( rmw
API) 是在顶层构建ROS所需的最小原始中间件功能集。不同中间件实现的提供者必须实现此接口以支持整个ROS堆栈。目前所有的中间件实现是对应不同DDS供应商。 [Alyssa@9876]
rmw
API 位于 ros2/rmw 仓库。 rmw
|包| 包含定义接口的C头文件,其实现由不同DDS供应商的各种rmw |包| 提供。 [Alyssa@9877]
有关 rmw
API 的定义,请参阅 API 文档: [Alyssa@9878]
rosidl
仓库 [Alyssa@9880]
rosidl
API 由一些与消息相关的静态函数和类型组成,并定义了不同语言的消息应生成哪些代码。API 中指定的生成的消息代码将是特定于语言的,但可能会也可能不会将生成的代码复用于其他语言。API 中指定的生成消息代码包含消息数据结构、构造函数、析构函数等内容。API 还将实现一种获取消息类型的类型支持结构的方法,该方法在发布或订阅该消息类型的话题时使用。 [Alyssa@9881]
有几个仓库在 rosidl
API 和实现中发挥作用。 [Alyssa@9882]
rosidl
仓库,位于 GitHub 的 ros2/rosidl ,定义消息IDL语法,如 .msg
文件、 .srv
文件等的语法,并包含解析文件的 |包| ,用于提供CMake基础设施以从消息生成代码,用于生成与实现无关的代码 (头文件和源文件),以及用于建立默认的生成器集。仓库包含以下 |包|: [Alyssa@9883]
rosidl_cmake
: 为从rosidl
文件(例如.msg
文件.srv
文件等)生成代码提供CMake函数和CMake模块。 [Alyssa@9884]rosidl_default_generators
:定义了默认生成器列表,确保它们作为依赖项安装,但也可以使用其他注入的生成器。 [Alyssa@9885]rosidl_generator_c
: 提供为rosidl
文件生成C头文件 (.h
)的工具 。 [Alyssa@9886]rosidl_generator_cpp
: provides tools to generate C++ header files (.hpp
) forrosidl
files. [待校准@9887]rosidl_generator_py
:提供为rosidl
文件生成Python模块的工具。 [Alyssa@9888]rosidl_parser
: 提供为解析rosidl
文件的Python API 。 [Alyssa@9889]
用于其他语言的生成器,例如 rosidl_generator_java
,是外部托管的 (在不同的仓库中),但将使用与上述生成器用于 "注册" 本身作为 rosidl
生成器相同的机制。 [Alyssa@9890]
除了上述 |包| 用于解析和生成 rosidl
文件的头文件之外, rosidl
仓库还包含与 文件中定义的消息类型的"类型支持" 有关的 |包| 。类型支持是指解释和操作由特定类型的ROS消息实例(例如,发布消息)表示的信息的能力。类型支持可以由编译时生成的代码提供,也可以根据 rosidl
文件的内容 (例如 .msg
或 .srv
文件) ,以及通过自检数据接收的数据编程实现。在后者的情况下,类型支持是通过消息的运行时解释来完成的,由ROS 2生成的消息代码可以与rmw实现无关。通过数据自检提供这种类型支持的包有: [Alyssa@9891]
rosidl_typesupport_introspection_c
: 为支持rosidl
消息数据类型生成C代码提供工具。 [Alyssa@9892]rosidl_typesupport_introspection_cpp
:为支持rosidl
消息数据类型产生C++代码提供工具。 [Alyssa@9893]
在类型支持是在编译时生成而不是以编程的方式生成的情况下,将需要使用特定于rmw实现的包。这是因为通常来说特定的rmw实现将需要以特定于DDS供应商的方式存储和操作数据,以便于DDS实现使用它。有关更多详细信息,请参见上面的 类型特定接口 部分。 [Alyssa@9894]
有关 rosidl
API (静态和生成) 中确切内容的更多信息,请参阅本页: [Alyssa@9895]
警告
待办事项: 链接到 rosidl
API 的定义 [Alyssa@9896]
rcutils
仓库 [Alyssa@9897]
ROS 2 C实用程序 ( rcutils
) 是一个C API ,由整个ROS 2代码库中使用的宏、函数和数据结构组成。这些主要用于错误处理、命令行参数解析和日志,它们不是特定于客户端或中间件层的,并且可以由两者共享。 [Alyssa@9898]
rcutils
API 和实现位于 GitHub 上的 ros2/rcutils 仓库中,其中包含C头文件接口。 [Alyssa@9899]
有关 rcutils
API 的完整定义,请参阅 它的API文档 [Alyssa@9900]