下面展示从listener.py的spin()到rcl库的调用流程,最新版ROS2代码的使用方式可能与图中展示的略有差异,但整体流程是不变的。
该listener.py代码中,使用create_subscription创建订阅者,调用rclpy.spin(node)后,程序会循环等待订阅话题中消息的到来:
listener.py会调用rclpy包提供的功能接口,进行消息的接受。
rclpy
是基于rcl
(ROS Client Library)构建的,而rcl
是ROS 2的核心客户端库,提供了跨语言的功能接口。
下面简要介绍rclpy
的一些关键概念和实现原理:
-
节点:在ROS 2中,节点是执行特定任务的最小单位。每个节点都是一个进程,并且能够与其他节点通信。在
rclpy
中创建节点非常简单,只需继承Node
类并重写必要的方法即可。 -
通信模型:ROS 2采用发布/订阅模式以及请求/响应模式进行节点间的通信。
rclpy
支持创建话题(Topics)上的发布者和订阅者,也支持通过服务(Services)来实现请求/响应模式的交互。 -
消息传递:消息是ROS 2中节点间通信的基本单元。在
rclpy
中,消息通常是定义好的类实例,这些类通常是从ROS接口包中导入的,如std_msgs.msg.String
。 -
事件处理:
rclpy
提供了一个事件驱动的架构,允许开发者通过回调函数来处理各种事件,例如接收到新的消息或服务请求。 -
执行器(Executors):在ROS 2中,执行器负责管理回调函数的调度。
rclpy
提供了几种不同的执行器类型,如单线程执行器(SingleThreadedExecutor
)或多线程执行器(MultiThreadedExecutor
),它们负责调用已注册的回调函数。 -
依赖项管理:为了支持跨平台使用,
rclpy
需要管理一系列的依赖库,包括通信中间件(如Fast-RTPS)和其他支持库。这些依赖通常通过构建系统(如colcon)自动安装。 -
异步编程模型:
rclpy
采用了异步编程模型,利用Python的asyncio
库来支持非阻塞的操作,这使得在编写高并发的ROS 2应用时更为灵活。
在这里要注意,rclpy并不是直接调用rcl成的代码,而是添加了一个中间层,该中间层,会生成一个类似_rclpy_pybind11.cpython-310-x86_64-linux-gnu.so的动态链接库,它是 rclpy
与 ROS 2 内核(主要是 C 语言编写的 rcl
库)之间的一个桥梁。这个文件是通过 PyBind11 库自动生成的,PyBind11 是一个强大的C++到Python的绑定库,它使得C++代码可以更容易地与Python交互。文件名中的 -310
表示它与 Python 3.10 版本兼容,x86_64-linux-gnu
表示该文件是为 x86_64 架构的 Linux 系统构建的。
当你使用 rclpy
包时,Python 解释器会自动加载这个共享对象文件,并且当你创建 ROS 2 节点或者执行其他 ROS 2 操作时,相关的函数调用会被传递给 _rclpy_pybind11.cpython-310-x86_64-linux-gnu.so
处理。
_rclpy_pybind11.cpython-310-x86_64-linux-gnu.so
会调用 ROS 2 的底层 C++ 功能(即rcl层),从而实现节点创建、话题发布等功能。
从rcl到rmw
从rcl层到rmw层的调用过程,同为C语言,直接调用rmw_take()即可。
从rmw到DDS
从rmw层到DDS的调用过程,rmw_take()的目的是借助DDS的实时发布-订阅(RTPS)传输机制,接收收到的消息。
rmw层接口对支持的厂家的DDS都有一套对应实现,下面以Fast DDS为例,观察rmw_take()是如何调用Fast DDS提供的接口的。
rmw_take()只是对__rmw_take()的封装,最后会走到_take()函数:
_take()函数中会调用eprosima::fastdds::dds:: DataReader类的take()函数,从话题中接收消息,并执行listener的回调函数:
到这里整个基于python的消息接受全部完成。
总结
ROS2中Python语言的调用关系呈现沙漏状,如下图所示。