Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

最近被 ROS2 action 弄得云里雾里,想要写一篇博客记录一下学习过程。
(以下笔记比较随意,算是我边学边唠叨的记录)


Action 的基本组成

一个 action 是由 requestresultfeedback 三个部分组成的:

1
2
3
4
5
# Request
---
# Result
---
# Feedback
  • Request:action client 发向 action server
  • Result:action server 发往 action client
  • Feedback:也是 server 发给 client 的,但它是周期性更新的、多条消息

脑海里冒出一堆问题:
这些和话题(topic)有什么区别?
是公用的话题还是只有 server 和 client 才能访问?
谁来处理 goal,谁来处理 feedback,谁来处理 result?

一个实例化的动作,叫做一个 goal(目标)

👉 参考: 如何创建一个动作(官方文档)


动作的定义位置

  • 必须要在 支持 C++ 的包 里面定义
  • 建议单开一个以 interfaces 结尾的包,用来放 action 定义

动作的两部分:Server 和 Client

Action 分为两部分:动作服务器(Server)动作客户端(Client)

创建动作服务器(Server)

先看 Python 的官方例子。

1
2
3
4
5
6
self._action_server = ActionServer(
self, # 加入这个 server 的结点
Fibonacci, # action 的类型
'fibonacci', # action 的名字
self.execute_callback # 执行“被接受的 goal”的回调函数
)

回调函数大致如下:

1
2
3
4
5
def execute_callback(self, goal_handle):
self.get_logger().info('Executing goal...')
goal_handle.succeed()
result = Fibonacci.Result()
return result

这里 goal_handle.succeed() 的作用是标记目标成功。

一个更完整的版本:

1
2
3
4
5
6
7
8
9
10
11
def execute_callback(self, goal_handle):
self.get_logger().info('Executing goal...')

sequence = [0, 1]
for i in range(1, goal_handle.request.order):
sequence.append(sequence[i] + sequence[i-1])

goal_handle.succeed()
result = Fibonacci.Result()
result.sequence = sequence
return result

异步与同步

我当时的困惑:
如果这个动作需要等待另一个进程的返回呢?会不会阻塞?

先澄清两个概念:

  • **Synchronous (同步)**:按顺序来,等一个完成了再做下一个
  • **Asynchronous (异步)**:可以同时处理,任务完成时再通知你

ROS2 的动作是 异步的

  • client 发 goal 之后,可以去做别的事
  • 过程中可以收到 feedback
  • goal 也可以随时取消

但是!如果动作内部调用了阻塞的操作,那 server 还是会卡住。
所以这就涉及到 future 和 Python 的 asyncio


Client 部分

定义 Client 时,需要指定 action 类型和名字:

1
self._action_client = ActionClient(self, Fibonacci, 'fibonacci')

在通信之前:

1
self._action_client.wait_for_server()

发送一个 goal:

1
return self._action_client.send_goal_async(goal_msg)

这里返回的就是一个 future


Future 是什么?

我理解中的 future = 咖啡店点单的小票。

你可能会见到:

  • _send_goal_future()
  • _get_result_future()

future 的意思就是“将来要得到的结果”,但现在就先开票。
当结果准备好时,会触发回调函数。


Asyncio 与 Future

如果 ROS2 自己不能避免阻塞,就得用 Python 的 asyncio

例子:

1
2
3
4
loop = asyncio.get_running_loop()
future = loop.create_future()
await future
confirmed_result = future.result()

要点:

  • async def 定义协程函数,不会立即执行
  • 执行方式:asyncio.run()await some_async_fn()

asyncio 三类对象:

  1. Coroutine(协程)
    async def 定义,是“异步逻辑的蓝图”。

  2. Task(任务)
    asyncio.create_task() 启动协程,后台运行。

  3. Future
    一个低层占位符,相当于一张提货单。

在 ROS2 action 的场景下:

  • 我们等待的其实是 外部消息的到来
  • 所以在 callback 里用 future.set_result(user_response) 唤醒之前等待的协程

Result 与 Goal 的区别

疑问:result 和 goal_handle 的状态有啥区别?

  • 在我的例子里,result 就是 goal 的执行结果
  • 有的情况:goal 成功,但 result 里有具体数值输出
  • 记得:执行 goal 的回调函数必须返回一个符合 action 类型定义的 result

那么如果 action 被 abort 了,它还会返回 result 吗?🤔
这个就需要进一步实验或查文档了。


参考资料


写到这里,我觉得对 ROS2 action 的理解清晰多了。
虽然一开始各种疑惑,但边学边写笔记还是挺有帮助的!

留下爪印