Skip to content

中间件实现(DDS)

ROS 中间件实现是一组软件包(packages),构成了 ROS 2 的底层通信框架。这些软件包与核心 ROS 2 接口(如 rmw、rcl 和 rosidl API)交互,从而将 Zenoh、DDS 等外部协议集成到 ROS 2 中。例如,rmw_fastrtps_cpp 将 eProsima 的 Fast DDS 实现适配到 ROS 2 的中间件 API,而 rmw_zenoh_cpp 为 Zenoh 协议提供了类似的集成。

有关 ROS 2 如何与不同中间件实现集成的更详细的实践指南,请参阅 中间件实现教程。

许多 ROS 2 中间件解决方案基于完整或部分兼容的 DDS 实现。例如,有些中间件实现使用 RTI 的 Connext DDS、eProsima 的 Fast DDS 和 GurumNetworks 的 GurumDDS。这些基于 DDS 的实现共享一些通用的软件包和模式。

在 GitHub 上的 ros2/rosidl_dds 仓库中,包含以下软件包:

  • rosidl_generator_dds_idl:提供工具,用于从 rosidl 文件(例如 .msg 文件、.srv 文件等)生成 DDS .idl 文件。

rosidl_generator_dds_idl 软件包为 ROS 软件包中的每个 ROS 接口定义文件(.msg、.srv、.action 等)生成 DDS .idl 文件。这些接口定义文件指定了 ROS 2 中话题(topic)、服务(service)和动作(action)使用的数据结构。基于 DDS 的 ROS 中间件实现随后使用这些生成的 .idl 文件来创建厂商特定的预编译类型支持(type support)。

基于 DDS 的 ROS 中间件实现通常在单个仓库中包含但不限于以下软件包:

  • <implementation_name>_cmake_module:包含用于发现和暴露所需依赖项的 CMake Module
  • rmw_<implementation_name>_<language>:包含特定语言(通常是 C++)的 RMW API 实现
  • rosidl_typesupport_<implementation_name>_<language>:包含用于为 rosidl 文件生成静态类型支持代码的工具,针对特定语言(通常是 C 或 C++)的实现进行定制

<implementation_name>_cmake_module 软件包包含中间件实现所需的 CMake Modules 和函数,用于查找底层依赖项。例如,rti_connext_dds_cmake_module 提供了围绕 RTI Connext DDS 附带的 CMake Module 的封装逻辑,以确保所有依赖于它的软件包选择相同的 RTI Connext DDS 安装。类似地,fastrtps_cmake_module 包含用于查找 eProsima Fast DDS 的 CMake Module,gurumdds_cmake_module 包含用于查找 GurumNetworks GurumDDS 的 CMake Module。并非所有实现都需要这样的软件包:例如,Eclipse 的 Cyclone DDS 已经自带了 CMake Module,其 RMW 实现可以直接使用,无需额外封装。

rmw_<implementation_name>_<language> 软件包以特定语言实现 rmw C API。实现本身可以用 C++ 编写,只需以 extern "C" 暴露头文件中的符号,使 C 应用程序也能链接。

rosidl_typesupport_<implementation_name>_<language> 软件包提供一个生成器,用于以特定语言生成 DDS 代码。它使用由 rosidl_generator_dds_idl 软件包生成的 .idl 文件以及 DDS 厂商提供的 IDL 代码生成器来完成这一过程。此外,它还会生成在 ROS 消息结构和 DDS 消息结构之间进行转换的代码。该生成器还负责为其使用的消息软件包创建共享库,该库与软件包中的消息类型和所使用的 DDS 厂商绑定。

如上所述,如果 RMW 实现支持消息的运行时解释,可以使用 rosidl_typesupport_introspection_<language> 代替厂商特定的类型支持软件包。这种在不预先生成代码的情况下以编程方式通过话题(topic)收发数据类型的能力,是通过支持 DDS X-Types Dynamic Data standard 实现的。因此,RMW 实现可以提供对 X-Types 标准的支持,或提供一个在编译时生成的、特定于其 DDS 实现的类型支持软件包。

DDS RMW 实现仓库的示例:

要通过 Zenoh 收发 ROS 2 数据,中间件软件包 rmw_zenoh_cpp 使用 zenoh-c 将 ROS 2 中间件 API 映射到 Zenoh 的 API。与基于 DDS 的实现不同,此中间件依赖 Zenoh router 来发现对等节点,并通过 Zenoh 的 ‘gossip scouting’ 机制传递发现信息。因此,rmw_zenoh_cpp 要求 Zenoh router(zenohd)在本地系统上处于运行状态或可通过网络访问。

在 ROS 2 的 Zenoh 集成中,每个 context 映射到一个 Zenoh session。该 session 在此 context 内的所有发布者(publisher)、订阅者(subscriber)、服务(service)和客户端(client)之间共享。该 context 维护一个本地图缓存,用于跟踪 ROS 2 实体的网络拓扑,每个实体的存在状态通过创建时发出、销毁时撤销的唯一活跃性令牌(liveliness tokens)来管理。

以下是 Zenoh 中间件 API 如何在其通信协议上适配 ROS 2 实体的非详尽列表:

Nodes(节点): 节点在 ROS 2 图中被称为”计算单元”,每个节点应负责单一的、模块化的功能。Zenoh 没有与节点直接对应的概念,因此 rmw_zenoh_cpp 不会为它们创建 Zenoh 实体。不过,当通过 RMW API 创建节点时,会声明一个类型为 NN 的活跃性令牌。

Publishers(发布者): ROS 2 发布者向特定话题(topic)发送数据。由于 Zenoh publisher 与 Keys 的功能非常相似,rmw_zenoh_cpp 直接映射这些实体。当通过 RMW API 创建发布者时,会声明一个类型为 MP 的活跃性令牌。

Subscribers(订阅者): ROS 2 订阅者监听话题上的新数据。它们在概念上等同于 Zenoh 中的 subscriber,因此 rmw_zenoh_cpp 直接映射这些实体。当新数据到达时,Zenoh 中间件软件包调用一个内部回调,该回调获取数据的所有权并向 rmw_wait 发出信号。当通过 RMW API 创建订阅者时,会声明一个类型为 MS 的活跃性令牌。

Service clients(服务客户端): rmw_zenoh_cpp 使用 Zenoh queryables 来实现 ROS 2 服务。客户端使用 rmw_send_request 发起请求。请求会携带用于关联响应的元数据,如序列号和发送该请求的客户端 GUID。随后,Zenoh 中间件软件包使用 z_get 向网络发送查询。当通过 RMW API 创建客户端时,会声明一个类型为 SC 的活跃性令牌。

Service server(服务端): rmw_zenoh_cpp 使用 Zenoh queryables 来实现 ROS 2 服务。ROS 2 节点使用 rmw_create_service 向网络通告服务,Zenoh API z_declare_queryable 用于创建 ROS 2 服务端表示。rmw_take_request 将查询传递给用户回调进行处理,计算完成后,rmw_send_response 将结果返回给请求者。创建服务端时,会声明一个类型为 SS 的活跃性令牌。

Zenoh 的 RMW 实现在 GitHub 上的 ros2/rmw_zenoh。