ROS 2内部接口
ROS内部接口是面向开发人员的公共C APIs,用于创建|客户端库|或添加新的底层中间件,但不适用于普通ROS用户。ROS |客户端库|提供了大多数ROS用户熟悉的面向用户的|APIs|,可以使用多种编程语言实现。
内部API架构概述
有两个主要的内部接口:
rmw
API 是ROS 2软件栈与底层中间件实现之间的接口。ROS 2使用的底层中间件可以是DDS或RTPS实现,负责发现、发布和订阅机制、服务的请求-响应机制以及消息类型的序列化。
rcl
API 是一个稍微高层的 API,用于实现 |客户端库|,不直接涉及中间件实现,而是通过 ROS 中间件接口(rmw
API)进行操作。
如图所示,这些 API 被堆叠在一起,以便典型的 ROS 用户可以使用 |客户端库| 的 |API|(例如 ``rclcpp``)来实现他们的代码(可执行文件或库)。 |客户端库| 的实现(例如 rclcpp
)使用提供对 ROS 图和图事件的访问的 rcl
接口。而 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_generator_cpp``或``rosidl_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特定的,但由于类型支持代码中的抽象,用户仍然无需直接接触到这些代码。
动态类型支持
实现类型支持的另一种方法是为诸如发布到主题之类的通用功能编写通用函数,而不是为每种消息类型生成函数的版本。为了实现这一点,这个通用函数需要一些关于正在发布的消息类型的元信息,例如消息类型中字段名称和类型的列表,以它们在消息类型中出现的顺序。然后,为了发布一条消息,您调用一个通用的发布函数,并传递要发布的消息以及包含有关消息类型的必要元数据的结构体。这被称为"动态"类型支持,与为每种类型生成函数的"静态"类型支持相对应。
上图显示了从用户定义的``rosidl``文件到生成的用户界面代码的流程。它与静态类型支持的流程图非常相似,只在类型支持生成的方式上有所不同,这在图的左侧表示。在动态类型支持中,``.msg``文件直接转换为用户界面代码。
这段代码也是中间件无关的,因为它只包含有关消息的元信息。实际执行工作的函数(例如发布到话题)对消息类型是通用的,并将根据需要调用特定中间件的API。请注意,与静态类型支持不同,不是由DDS供应商特定的软件包提供类型支持代码,而是每种语言都有一个中间件无关的软件包,例如``rosidl_typesupport_introspection_c``和``rosidl_typesupport_introspection_cpp``。软件包名称中的``introspection``部分指的是使用生成的元数据对任何消息实例进行内省的能力。这是允许实现诸如“发布到话题”等通用函数的基本能力。
这种方法的优点是所有生成的代码都是中间件无关的,这意味着可以在不同的中间件实现中重用它们,只要它们支持动态类型支持。它还会生成较少的代码,从而减少编译时间和代码大小。
然而,动态类型支持要求底层中间件支持类似的动态类型支持形式。对于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|位于|GitHub|_上的`ros2/rcl <https://github.com/ros2/rcl>`_仓库中,包含C头文件作为接口。rcl
C实现由同一仓库中的``rcl`` |package|提供。该实现通过使用``rmw``和``rosidl`` |APIs|来避免与中间件的直接接触。
有关完整的``rcl`` |API|定义,请参阅`rcl文档 <http://docs.ros.org/en/humble/p/rcl/>`_。
``rmw``存储库
ROS中间件接口(rmw
API)是构建ROS所需的最小原始中间件功能集。不同中间件实现的提供者必须实现此接口,以支持整个ROS堆栈。目前,所有中间件实现均为不同的DDS供应商提供。
rmw
|API|位于|GitHub|_上的`ros2/rmw <https://github.com/ros2/rmw>`_仓库中。rmw
|package|包含定义接口的C头文件,其实现由不同DDS供应商的rmw实现的各个|packages|提供。
有关``rmw`` |API|的定义,请参阅`rmw文档 <http://docs.ros.org/en/humble/p/rmw/>`_。
``rosidl``存储库
rosidl
|API|由一些与消息相关的静态函数和类型组成,以及对不同语言的消息生成的代码应该是什么的定义。|API|中指定的生成的消息代码将是特定于语言的,但可能会或可能不会重用其他语言的生成代码。|API|中指定的生成的消息代码包含诸如消息数据结构、构造、销毁等函数。|API|还将实现一种获取消息类型的类型支持结构的方式,在发布或订阅该消息类型的主题时使用。
有几个存储库在``rosidl`` |API|和实现中发挥作用。
rosidl``存储库位于|GitHub|_上的`ros2/rosidl <https://github.com/ros2/rosidl>`_,定义了消息IDL语法,即
.msg``文件、``.srv``文件等的语法,并包含用于解析文件、提供CMake基础架构以从消息生成代码、生成与实现无关的代码(头文件和源文件)以及建立默认生成器集合的|packages|。该存储库包含以下|packages|:
rosidl_cmake
:提供了用于从``rosidl``文件(例如``.msg``文件、``.srv``文件等)生成代码的CMake函数和模块。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``文件中的标头的 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 Utilities(rcutils
)是一个由宏、函数和数据结构组成的 C API,在整个 ROS 2 代码库中广泛使用。主要用于错误处理、命令行参数解析和日志记录,这些功能与客户端或中间件层无关,可以被两者共享。
rcutils
API 和实现位于 GitHub 上的 ros2/rcutils 代码库中,其中包含 C 头文件作为接口。
要完整了解 rcutils
API 的定义,请参阅 rcutils 文档。