ROS2中的Python语言支持(1)
在ROS 1中,所有客户端库都是“从头开始”开发的。这允许ROS1的Python客户端库纯粹用Python来实现,这会带来诸如不需要编译代码等好处。但是,不同客户端库之间的命名惯例和行为并不总是一致的,必须在多处进行漏洞修复,而且有很多功能只在一个客户端库中实现(例如UDPROS)。
ROS2希望解决上述问题,实现不特定于具体编程语言的ROS概念的通用功能,因此使用C语言开发了通用的RCL功能库(rcl),来实现不同开发语言间通用功能的统一。ROS2的C ++客户端库(rclcpp)和Python客户端库(rclpy)都是基于RCL中的通用功能库(rcl)开发的。
下图展示了ROS2的代码层次结构,可见,rclcpp和rclpy会依次经过rcl、rmw、dds,实现对DDS的调用。接下来会以talker和listener为例,展示ROS2中基于DDS的消息传输实现方式。

基于Python的Publisher
-
从rclpy到rcl
rcl是ROS2使用C语言编写的通用功能库,C++客户端库rclcpp调用rcl的接口很容易理解,Python客户端库rclpy是如何调用rcl的接口呢。下面展示了从talker.py的publish()到rcl库的rcl_publish()调用流程。

Python中的Publisher类的publish(),调用了self.__publisher.publish():

self.__publisher为rclpy_implementation.Publisher:

![]()

self.__publisher也就是从C库中调用的Publisher类,self.__publisher.publish()最终调用的是rclpy/publisher.cpp中的Publisher::publish()函数,该函数里调用了rcl通用功能库的rcl_publish():

-
从rcl到rmw
- 从rcl层到rmw层的调用过程,同为C语言,直接调用rmw_publish()即可。

rcl层调用的rmw_publish()函数为ROS2的rmw_implementation包中的functions.cpp通过RMW_INTERFACE_FN宏定义的rmw_publish()函数,根据RMW_IMPLEMENTATION宏设置的值,动态加载DDS的rmw_publish()函数。例如:RMW_IMPLEMENTATION=rmw_fastrtps_cpp时,会调用rmw_fastrtps_cpp包的rmw_publish()函数;RMW_IMPLEMENTATION=rmw_fastrtps_dynamic_cpp时,会调用rmw_fastrtps_dynamic_cpp包的rmw_publish()函数。例子中是Fast DDS的代码包,前者实现的是ROS2消息的静态类型机制,后者实现的是ROS2消息的动态类型机制。
-
从rmw到DDS
从rmw层到DDS的调用过程,rmw_publish()的目的是借助DDS的实时发布-订阅(RTPS)传输机制,将消息发布出去。

rmw是对各家DDS的适配,ROS2对支持的每家DDS的代码实现,都提供了一套rmw接口,应用程序通过调用rmw接口使用DDS通信服务。
ROS2支持过的DDS及对应的rmw实现如下图所示,目前的ROS2代码中有些DDS已经停止支持。本文以ros2/rmw_fastrtps的rmw实现为例,rmw_fastrtps适配的是Fast DDS实现的RTPS传输协议。

假设使用的是Fast DDS,那么上文中调用的rmw_publish()对应rmw_fastrtps的rmw_publish()函数:

Fast DDS同时实现了消息的静态类型机制和动态类型机制,两者的rmw_publish()函数实现是一样的。rmw_publish()函数的传入参数中:publisher对应上一章rclcpp::Publisher创建流程中最后创建的rmw_publisher_t实例,该实例的data->type_support_impl_成员保存了rosidl_message_type_support_t类型中的data成员,这是消息类型的关键信息;ros_message对应要发布的消息实例。

rmw_publish()函数内部调用了rmw_fastrtps_shared_cpp包的__rmw_publish()函数,rmw_fastrtps_shared_cpp是rmw_fastrtps_cpp和rmw_fastrtps_dynamic_cpp中公共接口的实现,用来实现相同代码的复用。
然后进入到rmw_fastrtps_shared_cpp::__rmw_publish()函数,该函数主要将ros_message赋值给了data.data,将publisher->data->type_support_impl_赋值给了data.impl(消息类型的关键信息),最终调用eprosima::fastdds::dds::DataWriter类的write()函数:

实际上会调用DataWriterImpl类的write()函数,其函数内部调用的是create_new_change()函数,进而调用create_new_change_with_params(),进而调用perform_create_new_change(),函数内部会调用TypeSupport类的serialize()函数:


serialize()函数是用来进行消息序列化的,根据is_cdr_buffer变量判断是否是标准CDR格式,根据前面代码可知is_cdr_buffer被赋值为false,会走TypeSupport类的serializeROSmessage()函数,该函数在rmw_fastrtps_cpp和rmw_fastrtps_dynamic_cpp中均有定义,在rmw_fastrtps_dynamic_cpp中的定义位于TypeSupportProxy类,TypeSupportProxy类是TypeSupport类的派生类,在rmw_fastrtps_dynamic_cpp的create_publisher()函数中,创建的是TypeSupportProxy实例,传入的实例是TypeSupportProxy类型时,调用的就是动态类型机制。
可见,ROS2通过rmw层来调用DDS提供的接口,在替换不同厂家的DDS后,rmw层接口的使用方法虽然没变,但这些接口的实现方式实际上会变为对应该厂家DDS的接口实现。
今天中秋节,祝所有程序猿中秋快乐!

更多推荐
所有评论(0)