为什么当前planning用service而state和assignment用topic

ROS2

Posted by Bruce Lee on 2025-03-28

为什么当前planning用service而state和assignment用topic

当前系统里, 为什么submit_taskplan_route走的是service, 但robot_states, task_assignments, task_statuses走的是topic

这个问题表面上像是在问ROS 2接口选型.

但如果真的把当前项目的代码结构和运行语义放在一起看, 它本质上问的是另一件事:系统里哪些交互是“必须有明确应答的请求”, 哪些交互是“应该持续流动的状态传播”

这两个问题如果不分开, 当前V1到V1.3这条主线其实很难写清楚.因为现在系统已经不是只有一个节点在自言自语了. 它至少同时存在这几类通信:外部客户端把任务送进系统,fleet_managerpath_planner索要route, fleet_manager把assignment发给robot,robot_agent持续回报自己的状态, manager和agent持续发出task_statuses

这些通信表面上都可以“传数据”, 但它们对语义的要求并不一样.


当前阶段目标

先把当前项目阶段说清楚.

README.md, STATUS.mdTECHNICAL_APPROACH.md看, 当前系统首先要成立的是一条很克制的协调主链路: 接收transport task/选一个当前可用robot/根据图模型拿到route/把route交给robot执行/持续观察执行状态直到完成

这里最重要的不是“所有接口都用同一种ROS 2模式”, 而是: 每一段交互都要选一种最贴近它自身语义的通信方式
否则会出现两种常见问题:明明是需要明确成败的请求, 却被做成模糊的消息往返,明明是应该自然流动的状态, 却被做成低效的轮询和阻塞调用

这就是为什么当前项目里不能只问“service和topic哪个更高级”, 而要问: 这个阶段的每一类数据, 到底在系统里扮演什么角色


先把当前通信面分成两类

如果只看当前代码, 系统里的通信大致可以分成两大类.

第一类: 有明确前置依赖的请求/应答

这一类最典型的就是:submit_task/ plan_route

它们的共同点是: 有一个发起方主动提问/有一个接收方必须给出明确结果/发起方后续行为会直接依赖这次结果

也就是说, 这类交互的核心不是“广播”, 而是: 当前这个请求到底有没有被接住, 有没有得到明确回答

第二类: 持续传播的状态和事件流

这一类包括: robot_states/task_assignments/ task_statuses

它们的共同点是: 数据会持续出现而不是一次性问答/接收方通常不止一个潜在订阅者 /更重要的是让系统里其他节点及时看到变化, 而不是一问一答完成事务

这类交互的核心是: 让状态和事件在ROS graph里自然流动

把这两类分清楚以后, 当前接口选型其实就没那么玄了.


为什么submit_task应该是service

先看最外层任务入口.

当前外部客户端把任务送进系统, 用的是submit_task service:

1
2
3
4
fleet_msgs/Task task
---
bool accepted
string message

这个设计在当前阶段非常合理, 因为客户端真正需要知道的不是“消息已经被发出去了”, 而是: manager有没有收到这个任务/manager有没有接受排队/当前返回的系统解释是什么

如果这里改成topic, 会马上出现几个工程问题.

第一, 提交方拿不到明确受理语义

如果只是向某个topic发布任务, 那客户端最多知道:

  1. 我把消息发出去了

但它不知道: 当前manager是否在线/manager是否真的消费到了/manager是否接受这个任务进入队列

而当前V1系统里, “任务有没有被系统接住”本身就是一个非常关键的边界.

所以这里需要的不只是传输, 而是: 受理确认

这正是service天然擅长的语义.

第二, 任务提交不是持续流, 而是离散事务

submit_task不是每秒几十次自动上报的状态流.

它更像一个操作命令: 用户现在提交一个task/系统立即告诉你是否已排队

这类交互天然更接近RPC风格.

第三, 它能把“接入成功”和“真正执行成功”分开

当前submit_task返回accepted = true, 本质上只代表: manager已经把任务接住并放进内部处理流程

它并不承诺: task一定能被规划/task一定能立刻被分配/task一定会执行完成

这个边界非常重要.

因为当前系统后面还有: planner rejection/reservation blocking/waiting/执行中与completed状态回传

也就是说, service在这里承担的是“入口事务确认”, 而不是整个生命周期承诺. 这恰好符合当前主链路的分层方式.


为什么plan_route更应该是service而不是topic

第二个更典型的service就是plan_route.

这个接口在当前系统里更关键, 因为fleet_manager能不能继续往下走, 取决于planner这次有没有返回route.

当前manager的逻辑其实非常清楚: 选出一个idle robot/取它的current_waypoint/调用plan_route/若返回成功route, 才发布TaskAssignment/若失败, 则进入failedwaiting之类的分支

这意味着planner当前不是一个“顺手算一下”的旁路模块, 而是: dispatch关键路径上的前置依赖

这种情况下, service比topic更自然, 原因至少有四点.

第一, manager就是需要一次明确答复

当前manager不是在说: 谁愿意帮我规划一下都行

而是在说: 对这个robot, 这个pickup, 这个dropoff, 你现在能不能给出route

它需要的是一个有明确边界的回答: success/message/route_waypoints

这就是标准的request/response语义.

第二, 规划结果直接决定manager下一步动作

当前系统里, 规划结果不是“给你参考一下”, 而是直接控制下面这些分支: 发布assignment/丢弃失败任务/或者因为reservation阻塞而转成waiting

也就是说, 当前planner结果会立刻参与控制流.

这种前置依赖如果硬改成topic request/reply, manager反而要自己维护: 请求ID/超时/结果匹配/重复响应去重

这在当前阶段完全没有必要.

第三, 当前规划是短时操作, 不需要action语义

如果规划过程是很长时间的异步运算, 甚至需要取消, 反馈进度, 那当然可以考虑action.

但现在的planner只是图上的BFS加reservation判断.它的特征是: 快/确定/一次调用只要一个完整结果

所以service正好合适.

第四, 它能保持planner与manager的职责边界清楚

当前manager不做图搜索, planner也不直接做任务调度.

service把它们之间的边界切得很清楚: manager提出一个规划问题/ planner返回一个规划答案

这种分法对当前项目尤其重要, 因为这个项目现在还处在“先把架构主链路做清楚”的阶段.


为什么robot_states天然更适合topic

再看robot_states.

这个接口如果做成service, 当前系统反而会显得很奇怪. 因为robot state的本质不是“有人来问, 我才答”, 而是: robot应该持续把自己的当前状态发布出来
这里面最关键的是“持续”两个字.

第一, 状态天然就是时间流

当前robot_agent每秒发布一次状态, 里面包含: robot_id/ pose/ current_waypoint/ status/ battery_percent/ current_task_id

这说明它表达的是: 我现在在哪里/我现在在干什么/ 我现在手上有没有任务

这种数据不是离散事务, 而是时间序列. 而topic天生就适合表达这种“系统此刻是什么样”的流动信息.

第二, manager不应该轮询每个robot

如果改成service模式, manager就要不断去问: b robot_1你现在状态是什么/robot_2你现在状态是什么/下一秒再问一遍

这不仅更笨重, 还会把状态获取改成manager主导的轮询模式.

而当前更自然的做法是: robot自己主动广播/manager只负责订阅并缓存最新状态

这也是典型的fleet系统思路.

第三, 未来潜在订阅者不只manager一个

即使当前主要消费者是fleet_manager, 后面很可能还会有: 可视化节点/监控节点/调试工具/任务看板

如果state是topic, 这些订阅者都可以自然接入. 如果state是service, 整个系统就会更像“谁想看就自己来轮询”, 结构会更僵.

所以从演化空间看, topic也更合适.


为什么task_assignments当前也更适合topic

很多人看到assignment时, 第一反应会是: 这不是一个命令吗/命令为什么不用service或者action

这个问题是合理的.

但放在当前项目阶段里, topic依然是一个务实选择.

第一, assignment当前更像系统广播里的定向消息

fleet_manager当前发布的是TaskAssignment:

1
2
3
string robot_id
fleet_msgs/Task task
string[] route_waypoints

虽然这是发给某个robot的, 但它的传输方式仍然是: manager发布到task_assignments/每个robot_agent都能收到/只有robot_id匹配的那个agent真正处理

这说明当前assignment的语义是: 在共享topic上发布一条带目标标识的任务指令

对于V1阶段, 这已经足够成立.

第二, 它让manager保持简单

如果把assignment做成每个robot一个service, manager就要额外处理: 每个robot自己的service地址,robot当前是否在线,发送失败与重试,更复杂的耦合关系

当前系统只是两个demo robot, 用topic广播加本地过滤, 反而是最省事也最符合ROS 2直觉的办法.

第三, 当前assignment还不需要action那套完整控制面

如果后面要支持: cancel task/preempt task/feedback stream/result confirmation

那么action就会越来越合理.

但当前agent执行模型还比较克制: 接一个assignment/保存整条route/周期推进waypoint/通过robot_statestask_statuses对外体现进展

也就是说, 当前assignment本身并不承担完整生命周期控制, 它只是: 把一次带route的执行指令送到目标robot

这时候topic完全够用.


为什么task_statuses也应该是topic

task_statuses在当前系统里其实很有意思.

因为它不是robot state那种严格周期性的流, 也不是service那种一次请求一次回答.

它更像是: 任务生命周期里的事件流

当前系统里已经出现过这些状态: queued/assigned/executing/waiting/completed/ failed

这类数据最适合topic, 原因在于它不是为了让某个调用方立即拿到单次应答, 而是为了让整个系统知道: task刚刚发生了什么状态变化

也就是说, task_statuses的价值主要在于: 让验证脚本能观察生命周期,让未来监控工具能订阅事件,让manager和agent以松耦合方式共同向外暴露进展

如果把它做成service, 反而会很别扭.

因为service更像在回答: 你现在问我, 我就给你一个快照

task_statuses更想表达的是: 当状态变化发生时, 我就把事件流出去

这是两种完全不同的接口哲学.


当前这套分层真正解决了什么

如果把当前通信分工抽象一下, 它其实已经形成了一个很清楚的结构: submit_task service负责把外部输入可靠接入系统. plan_route service负责manager到planner之间的同步前置依赖. robot_states topic负责持续状态广播. task_assignments topic负责执行指令分发. task_statuses topic负责生命周期事件流

这套分层最大的好处是: 请求类交互和状态类交互没有被混在一起

于是当前系统就可以同时做到: 对关键入口和关键依赖有明确应答.对运行时状态和任务事件保持持续可观察性

这正是当前V1到V1.3阶段最需要的东西.

因为这个阶段要先解决的是: 主链路可解释, 行为可观察, 模块边界清楚

而不是过早追求更复杂的控制协议.


但这套接口选择不是终局

这里也不能把当前做法神化.

它适合的是当前阶段, 不是所有阶段.

第一, assignment后面可能会逐渐逼近action语义

一旦系统开始支持: 取消任务/抢占任务/ 更细粒度feedback/manager等待明确执行结果

task_assignments只靠topic加robot_states / task_statuses旁路反馈, 就会开始显得不够强.

换句话说, 当前assignment是“简单且够用”, 不是“最终形态”.

第二, task_statuses未来可能需要持久化查询接口

现在task_statuses只是live topic stream.

这意味着: 订阅晚了就看不到之前的历史/想查某个task最终状态, 目前没有专门query API

所以后面如果操作侧真的需要查历史, 可能还要加: 状态持久化/查询service/或者单独的任务历史节点

第三, planner如果变成长时可取消计算, service就未必还够

当前用service是因为planner又快又同步.

但如果后续变成: 带冲突消解的复杂搜索/需要较长计算时间/支持中途取消和重规划

那就很可能要重新评估action是不是更合适.

也就是说, 当前接口选型并不是教条, 而是: 服务于当前阶段问题规模的工程折中


当前这篇专题最重要的结论

如果只用一句话总结, 那就是: 当前ros2-fleet-coordinator不是随手混用了service和topic, 而是在按照“请求有明确应答, 状态持续流动传播”的原则做分层

所以你会看到: 任务接入和路径规划走service
.机器人状态, assignment分发和任务生命周期走topic

这套分法和当前项目的目标是对齐的.

因为当前项目真正要先打通的, 不是复杂控制协议, 而是: task如何被系统接住/route如何成为dispatch前置条件/execution如何被持续观察/生命周期如何被外部验证

把这些问题放回系统语义里看, 当前接口选择其实相当克制, 也相当合理.


If you like this blog or find it useful for you, you are welcome to comment on it. You are also welcome to share this blog, so that more people can participate in it. All the images used in the blog are my original works or AI works, if you want to take it,don't hesitate. Thank you !