
1. 一个 Float64 话题,十种语义
早期图方便,/target_distance 用 std_msgs/Float64 广播目标距离。半年后:A 节点发 mm,B 当 m,C 在回调里除以 1000「因为上次那个驱动是这样」。接口没有单位与 frame,破坏性变更被摊进每个订阅者的 if。.msg/.srv/.action 改一次,全语言绑定与 bag schema 一起震——接口是架构外骨骼,应按演进成本定价。Code review 看到新裸 Float64 topic 应直接拒,要求结构化 msg。
2. 消息:字段即文档
# TargetPose.msg — 宁可啰嗦,不要口头传统
geometry_msgs/PoseStamped pose
float64 approach_distance_m
uint8 schema_version原则:单位进字段名(_m、_rad、_s);坐标进 frame_id 或嵌 PoseStamped;状态用 uint8 常量而非自由字符串(字符串留给日志)。大数组问一句:是否应拆 topic 或走零拷贝/loan——把 4K 图像塞进 srv request 通常是错的。
uint8 STATUS_IDLE=0
uint8 STATUS_RUNNING=1
uint8 STATUS_FAULT=2
uint8 status3. Service 与 Action:错误要可机器读
# Calibrate.srv
string sensor_id
---
bool success
uint16 error_code # 0=OK, 0x1001=timeout, 0x1002=bad_sample
string message # 给人看message 给工程师,error_code 给状态机与重试策略。只有 string 时,客户端只能 substring 匹配「timeout」,国际化后再崩一次。
Action 的 result 同理;cancel 与 feedback 频率要在接口层约定,避免每个实现各发各的。
4. 演进:添字段 < 改语义 < 改类型名
- 添 optional 字段(ROS 2 IDL 新字段有默认):最便宜,旧 bag 可播
- 改字段语义(同一
distance_m从欧氏变沿路径):等价破坏性,应升schema_version或新类型名 - 改类型名 / 包名:最贵,但最干净
ros2 interface show my_robot_msgs/msg/TargetPose
ros2 interface package my_robot_msgs发布说明写清:哪些旧 bag 还能播、哪些字段被弃用。下游若缓存了反序列化 struct,添字段仍可能要求 redeploy——接口变更通知应走 fleet 通道,不能只在接口仓库 merge 完就算完。
嵌套 geometry_msgs 时 frame 冲突是常见 bug:外层 PoseStamped 与内层 Point 各带 frame,文档写清以哪个为准,避免 AMCL 与 costmap 各读各的。
5. 版本与弃用窗口
预计会变的消息留 schema_version 或独立 v2 包。弃用流程:双发(旧+新 topic)→ 日志 warning → 两 release 后删旧。接口仓库的破坏性 PR 应比算法仓库更难合并:必须附迁移指南与下游 grep 证明。
6. 契约测试
CI 对关键接口跑:
- 下游最小订阅/客户端能否编译链接
- 序列化 round-trip(Python ↔ C++)
- 字段默认与文档一致
新节点不得再 pub 裸 Float64 承载结构化状态——存量可以迁,增量要拦。
7. 与录包、多语言团队
bag 存的是 schema;接口漂移后旧袋「能播但语义变了」比不能播更危险。多语言(C++/Python/Go 绑定)共享同一 .msg 源,口头传统进不了 Go 的 struct。接口评审问三句:单位?frame?失败时 code 是什么?
跨仓库依赖时,接口包应独立 semver:下游 pin 主版本,避免算法仓库顺手改 msg 字段导致 fleet OTA 半升级。
8. 验收
- 随机抽 5 个 srv:result 含 machine-readable code
- 新增字段不破坏旧 subscriber(默认值可验证)
ros2 interface list与架构图 topic 类型一致,无平行 Float64 语义- 破坏性变更有 migration 文档与双发窗口记录
9. 案例:改语义未升版本
BatteryState.percentage 从 0–100 改成 0.0–1.0,类型仍是 float32,下游电量门限全错,现场低电未告警。此后 percentage 改名 state_of_charge_fraction,旧名 deprecated 两 release。改语义即新类型或新字段,不偷偷改约定。
10. 默认策略
先想一年后的破坏性变更谁买单;单位、frame、error_code 今天写进契约。算法可以周更,接口按季度思考——方向反了,「快速迭代」会碎在十个平行语义里。
11. Action 与长时间任务
导航、充电、标定等长任务用 Action,feedback 频率与字段含义要写进 .action 注释或独立文档——例如 progress 是路径百分比还是时间百分比。Cancel 语义(软停 vs 硬停)必须在 result code 区分,否则客户端无法安全重试。Service 适合毫秒级原子操作;把 30 s 标定塞进 srv 会占满 executor 并阻塞同节点其他服务。
相关
也可以看看
- ·15 分钟阅读
ContentFilteredTopic:把过滤下推到中间件还是应用层
对比订阅端丢弃、应用层条件判断与 DDS 内容过滤的 CPU/带宽边界,说明表达式能力与发现时序限制;用高频率噪声话题与延迟尖峰验收过滤位置选择。
- ·16 分钟阅读
ROS 2 Action 取消语义:收到 cancel 不等于执行器已经停下
梳理 goal handle 状态机、取消回调与硬件停止之间的异步边界;用可中断执行循环、截止时间和终态发布构建契约并以注入验收。
- ·9 分钟阅读
ROS 2 大消息内存路径:intra-process、loaned message 与 shared memory 不是一回事
以 4K 图像和点云管线为例,逐段核对 rclcpp、RMW 与进程边界上的所有权、分配和真实拷贝。
johan's blog