最近被 ROS2 action 弄得云里雾里,想要写一篇博客记录一下学习过程。
(以下笔记比较随意,算是我边学边唠叨的记录)
Action 的基本组成
一个 action 是由 request、result 和 feedback 三个部分组成的:
1 | # Request |
- 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 | self._action_server = ActionServer( |
回调函数大致如下:
1 | def execute_callback(self, goal_handle): |
这里 goal_handle.succeed() 的作用是标记目标成功。
一个更完整的版本:
1 | def execute_callback(self, goal_handle): |
异步与同步
我当时的困惑:
如果这个动作需要等待另一个进程的返回呢?会不会阻塞?
先澄清两个概念:
- **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 | loop = asyncio.get_running_loop() |
要点:
async def定义协程函数,不会立即执行- 执行方式:
asyncio.run()或await some_async_fn()
asyncio 三类对象:
Coroutine(协程)
用async def定义,是“异步逻辑的蓝图”。Task(任务)
用asyncio.create_task()启动协程,后台运行。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 官方教程
- 异步调用 future/promise 模式(C++版本)
写到这里,我觉得对 ROS2 action 的理解清晰多了。
虽然一开始各种疑惑,但边学边写笔记还是挺有帮助的!