260325

目前为止,我们已经完成了 ROS2 官方的初学者教程的全部内容,耗时约一周左右。

接下来就可以进入到中等难度的教程了,虽然说是中等,但是大概率也难不到哪里去。

首先是rosdep这个 ROS2 官方的依赖管理器。

这个东西虽然很有用, 但是对我们来说用处不大, 因为我们在进行 python 开发(尤其在进行机器学习相关开发的时候)的时候通常需要精确管理版本, 而 package.xml 通常难以这样管理 python 依赖,因此我们主要还是得使用 pyproject.toml 或 requirements.txt 来进行管理, 而非使用 package.xml 来进行管理。

简单的来说:

  • package.xml 负责 ROS 包层面的依赖声明
  • pyproject.toml / requirements.txt 负责 Python 环境,尤其是精确版本控制

以上。

接下来我们就可以去写一下自定义动作(Action)了。

通常来说,一个 .action文件的定义如下:

# 请求
---
# 结果
---
# 反馈

即由三部分组成,分别是请求、结果和反馈,中间使用 --- 进行分隔。

我们可以在我们之前的 tutorial_interfaces这里创建一个计算斐波那契数列的src/tutorial_interfaces/action/Fibonacci.action动作文件:

int32 order
---
int32[] sequence
---
int32[] sequence

这里我们的请求是数列的前N项,返回的结果是整个数列,反馈是目前已经计算出的数列。

然后在 CMakeLists.txt中的 rosidl_generate_interfaces 这里添加"action/Fibonacci.action"。

接下来进行构建即可使用接口查看工具来找到我们这个接口的定义:

(.venv) jese--ki@KiBall:~/Projects/learn/ros2/pub_sub$ ros2 interface list | grep "Fibonacci"
    example_interfaces/action/Fibonacci
    tutorial_interfaces/action/Fibonacci

然后我们就可以看看整个接口的定义了:

(.venv) jese--ki@KiBall:~/Projects/learn/ros2/pub_sub$ ros2 interface show tutorial_interfaces/action/Fibonacci
int32 order
---
int32[] sequence
---
int32[] sequence

以上。

除了创建动作接口以外,我们肯定还要调用这个接口的,我们可以在 py_srvcli这个包里继续创建动作的提供者和客户端。

首先是 src/py_srvcli/py_srvcli/service_member_action.py:

import rclpy
from rclpy.action import ActionServer
from rclpy.executors import ExternalShutdownException
from rclpy.node import Node

from tutorial_interfaces.action import Fibonacci


class FibonacciActionServer(Node):

    def __init__(self):
        super().__init__('fibonacci_action_server')
        self._action_server = ActionServer(
            self,
            Fibonacci,
            'fibonacci',
            self.execute_callback)

    def execute_callback(self, goal_handle):
        self.get_logger().info('Executing goal...')
        result = Fibonacci.Result()
        return result


def main(args=None):
    try:
        with rclpy.init(args=args):
            fibonacci_action_server = FibonacciActionServer()

            rclpy.spin(fibonacci_action_server)
    except (KeyboardInterrupt, ExternalShutdownException):
        pass


if __name__ == '__main__':
    main()

这里我们创建了一个节点以及一个动作提供者,可以看到我们的动作 Action和话题Topic、服务Service等都不同,另外两者都是通过节点内部的创建方法就可以直接进行创建,而动作则需要专门使用一个类进行实例化且需要刻意传入一个节点(我们这里是 self,即该节点自身)。

因为在 ROS2 的底层中,话题和服务都是一等公民,而动作则是一个极其复杂的缝合怪。

它本质上是一个状态管理器,同时是一个打包的话题+服务。它自身在底层并没有专门的 DDS 通信机制,都是新创建三种(总共五个)底层通道:

  1. 目标服务(Goal Service):客户端发送任务,服务端回复“接受”或“拒绝”。
  2. 结果服务(Result Service):客户端请求最终结果,服务端在任务结束时返回。
  3. 反馈/取消/状态话题(Feedback Topic):服务端在执行任务的漫长过程中,不断向外高频广播进度(比如“已经走了 50%”),以及取消请求和状态机的话题。

因此我们这里需要手动创建一个实例,而非直接在节点内部就可以创建。

这里我们创建这个实例的时候,传入了下面四个参数:

  1. self节点作为提供者进行执行。
  2. 动作类型Fibonacci。
  3. 动作名称fibonacci。
  4. 该动作的执行回调self.execute_callback,该动作被调用的时候将作为实际执行动作的函数来进行回调。

值得注意的是,ROS2 官方文档这里写的有问题,应当是 A ROS 2 node to add the action server to: self.,而非 A ROS 2 node to add the action client to: self.: