前言
本章是详细介绍ROS2通信中间件中rclpy模块软件框架。
不了解背景的同学请先看:
rclpy_4">rclpy软件框架
rclpy :ROS Client Library for the Python language.
上面这句话清楚的描述rclpy对于ROS的功能定位。
ROS2提供三种通信模式:Topics、Services、Actions,我们就以这三种视角进行代码架构学习
rclpy communication" />
总体软件层级
下面展示我划分的rclpy 软件架构图,先以Services的通信方式为例(后面我会把所有模块补上)进行讲解
rclpy module architecture" />
我把rclpy module划分三层,Application API、API Python Business、API C++ Business。
- Application API:上面对接User Application,对其提供直接调用的接口、比如:create_client。其中__init__比较特殊代表__init__.py文件,这个文件主要功能包含:提供 init 函数,用于初始化 ROS 2 通信,包括设置上下文、处理命令行参数、设置信号处理程序。当写User Application代码时必须写到这样一行代码:
rclpy.init(args=args)
这行代码的目的就是对__init__.py问价中init函数接口初始化。
- API Python Business:支撑Application API功能层级
- API C++ Business:通过C++方式实现支撑整个python API功能层级。提供的方式是把整个C++模块编译为_rclpy_pybind11 库让Python使用。其中在_rclpy_pybind11.cpp文件中将 ROS 2 的 C++ 接口绑定到 Python,从而使得 Python 用户可以使用 C++实现业务功能。
// Provide singleton access to the rclpy C modules.
rclpy_implementation = import_c_library('._rclpy_pybind11', package)
Services框架
Services通信方式的功能主要是由Server端和Client端进行实现,
User Application层级中Server和Client主干逻辑代码:
//Client.py
class adderClient(Node):def __init__(self, name):super().__init__(name) # ROS2节点父类初始化self.client = self.create_client(AddTwoInts, 'add_two_ints') # 创建服务客户端对象(服务接口类型,服务名)while not self.client.wait_for_service(timeout_sec=1.0): # 循环等待服务器端成功启动self.get_logger().info('service not available, waiting again...') self.request = AddTwoInts.Request() # 创建服务请求的数据对象 def send_request(self): # 创建一个发送服务请求的函数self.request.a = int(sys.argv[1])self.request.b = int(sys.argv[2])self.future = self.client.call_async(self.request) # 异步方式发送服务请求//Server.py
class adderServer(Node):def __init__(self, name):super().__init__(name) # ROS2节点父类初始化self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.adder_callback) # 创建服务器对象(接口类型、服务名、服务器回调函数)def adder_callback(self, request, response): # 创建回调函数,执行收到请求后对数据的处理response.sum = request.a + request.b # 完成加法求和计算,将结果放到反馈的数据中self.get_logger().info('Incoming request\na: %d b: %d' % (request.a, request.b)) # 输出日志信息,提示已经完成加法求和计算return response # 反馈应答信息
其中有有几个重要的接口函数:create_client、create_service、call_async,再加一个在创建Server对象之后会调用的函数接口,rclpy.spin(参数为:Server class对象)。三个函数下面会讲解函数调用流程图。
CLient 调用Server流程
- User Application创建调用create_client创建出真正实现功能的class CLient,然后再调用call_async发送消息到Server,通过_rclpy_pybind11 C++ lib中实现的C++ class CLient对象,最后再通过rcl_send_request接口调用进入rcl 层级。
Server回复CLient流程
- User Application调用create_service创建出class Service,并且在Service中注册回调函数(如上面代码中的:adder_callback),当执行完回调函数处理数据逻辑之后,return response就会调用send_response、继续调用service_send_response到C++ class Service,最后调用rcl_send_response进入rcl module。
Server回调函数触发流程
这个时候我们就会想弄清楚Server注册的回调函数是怎么样被触发的呢?接下来我们就来继续探究!
- Server在创建完对象之后会调用spin,这个接口功能是在init中实现,并且获取到SingleThreadedExecutor实例调用spin_once、_spin_once_impl
- 再调用wait_for_ready_callbacks进入到基类Executor,最后的主干逻辑是在_wait_for_ready_callbacks函数中实现,其中会调用_rclpy.WaitSet,这个函数实现是在API C++ Business中class WaitSet。
- class WaitSet会调用rcl_wait_set_init进入到rcl Layer,会对rcl wait实例化分配空间创建初始化为subscriptions(DDS 概念),并且创建一个item,并且加入到wait队列中,去真正的等待CLient发送call请求。
当wait被回调函数唤醒之后,会去遍历收到哪个实例的消息:
# get ready entities
subs_ready = wait_set.get_ready_entities('subscription')
guards_ready = wait_set.get_ready_entities('guard_condition')
timers_ready = wait_set.get_ready_entities('timer')
clients_ready = wait_set.get_ready_entities('client')
services_ready = wait_set.get_ready_entities('service')
当Server接收到是clients发送的消息,会解析其中的内容:
for srv in node.services:if srv.handle.pointer in services_ready:if srv.callback_group.can_execute(srv):handler = self._make_handler(srv, node, self._take_service, self._execute_service)yielded_work = Trueyield handler, srv, node
执行_execute_service函数体,其中会触发Server callback函数调用,返回之后调用send_response进入到Server给Client发送消息的熟悉环节。
#使用await_or_execute触发Server callback执行完毕,接收到response之后把response作为参数,调用send_response发送response到Client
async def _execute_service(self, srv, request_and_header):if request_and_header is None:return(request, header) = request_and_headerif request:response = await await_or_execute(srv.callback, request, srv.srv_type.Response())srv.send_response(response, header)
Topics框架
Actions框架
未完待续!持续更新!欢迎大家关注!!!
参考文献
- User Application代码都是参考:ROS2入门21讲