接口设计:五个节点之间的"契约"怎么签
架构画好了,节点之间拿什么说话?本实验掌握 A/C/D/E/F 五大系列接口的名称、类型、方向与消息类型,画出"意图→任务→执行→完成播报"的完整闭环调用链,解读 config/eai_factory.yaml 的参数化与意图映射(改 yaml 不改代码),并学会用 ros2 命令核对"文档 ↔ 代码"的一致性。
01问题引入:接口对不上,联调全报废
两个小组并行开发:A 组写语音、B 组写调度。联调那天——
/eai_factory/intent 发了 "开始装货"(中文句子),B 组在等 "load"(英文枚举)→ 对不上暗号02原理通俗讲解:传菜条的写法与后厨暗号
餐厅的传菜条(接口)怎么写才不出错?三条生活经验直接对应三条设计规律:
状态类(跑没跑、安不安全)用 String 枚举(idle/running/sensor_blocked、safe/danger/recover);布尔信号用 Bool(interrupt/danger);办手续用 Trigger(回执 success+message);坐标位姿用 PoseStamped。看到新接口先猜类型,八九不离十。
"装货开始了"这种叫一声人人听见的用话题(intent/speak/status/danger);"把这批货抓了并等你回话"这种必须等回执的用服务(start_load、grasp/start)。喊话不保证听到,立字据保证有回执。
服务返回 success 只代表"任务已受理"('load task triggered'),机械臂还在动;真正完成要等 status/state 话题通知。预约餐厅拿到排队号(触发)≠ 菜已上桌(完成)。
"装货"这个词对应哪个服务?写在 yaml 的 intents 段(load→/eai_factory/start_load)。加个新意图 = 加三行配置,一行代码不用动——后厨加菜只改菜单不改流程。
pick/place/go_work/set_motor 这些是复用包既有接口(D 系列)——eai_factory 只调用不重写。清单表里标注"复用",避免误以为是自家定义。
所有对外播报("装货完成")都经 N2 发 A2 speak、由 N1 的 TTS 念出——出口收敛到一个接口,文案才管得住(R7)。
03关联知识点:五大系列接口清单
| # | 接口 | 类型 | 方向 | 消息 | 语义 / 说明 |
|---|---|---|---|---|---|
| A1 | /eai_factory/intent | 话题 | N1→N2 | std_msgs/String | 意图:"load"/"sort"/"palletize" 英文枚举 |
| A2 | /eai_factory/speak | 话题 | N2→N1 | std_msgs/String | 固定文案播报出口 |
| A3 | /eai_factory/interrupt | 话题 | N1→N2 | std_msgs/Bool | 语音打断(R8 四步复位入口) |
| C1 | /eai_factory/safety/danger | 话题 | N3→N2,N-grasp,N-conveyor | std_msgs/Bool | true=危险(释放力矩),false=解除(恢复+回work) |
| C2 | /eai_factory/safety/status | 话题 | N3→观察者 | std_msgs/String | safe / danger / recover 状态枚举 |
| C3 | /car1_base_driver/bus_servo/set_state | 话题 | N3→底盘驱动 | SetBusServoState | 力矩释放/恢复(enable_torque 6 舵机) |
| C4 | /arm_controller/go_work | 服务 | N3→复用包 | std_srvs/Trigger | 回 work 姿态(解除危险后调用) |
| D1-D7 | 复用系列 | 服务/话题 | N2→复用节点 | Trigger / PoseStamped 等 | arm pick/place/set_gripper、set_motor、set_pick_target 等(复用不改) |
| E1-E3 | /eai_factory/start_load|sort|palletize | 服务 | →N2 | std_srvs/Trigger | 任务触发(触发语义,如 'load task triggered') |
| F1-F9 | 流水线系列 | 服务/话题 | →N-conveyor | Trigger / String / Bool | 含 F3 run_until_sensor(异步:运转至光电遮挡→定点停,完成走 state 话题)、F4 emergency_stop(同步立即生效) |
| F10 | danger 联锁 | 话题 | N3→N-conveyor | std_msgs/Bool | 危险即急停:流水线停 + 调 /arm_controller/emergency_stop("双停") |
yaml 参数化(config/eai_factory.yaml,/**: ros__parameters: 顶层下发)关键段落:
mode: production # L7:debug/production 双模式 vision: { tag_id: 3, tag_size: 0.025, tag_family: tag36h11 } # L56-L63 grasp.tasks: # L65-L90 每任务 [x,y,z,pitch] 四元组 + vision 标志 load: { vision: true, observe_pose: [0.16,0.0,0.27,2.5307], place_pose: [0.05675,-0.13166,0.20386,2.93322] } sort: { vision: false, observe_pose: [0.0622,-0.1158,0.1951,3.0371], place_pose: [0.1434,-0.1296,0.2075,2.7283] } palletize: { vision: false, observe_pose: [0.145,0.0027,0.1850,3.1381], place_pose: [0.0608,0.1345,0.2051,2.6461] } safety: { front_start_deg: -30, front_end_deg: 30, danger_dist: 0.20, persist_time: 0.5 } # L92-L98 intents: # L110-L117 意图 ↔ 服务名 表驱动映射 load: { action: "/eai_factory/start_load" } sort: { action: "/eai_factory/start_sort" } palletize: { action: "/eai_factory/start_palletize" }
grasp.tasks.*.vision 标志决定抓取模式:load 为 true 走视觉抓取(AprilTag 定位)、sort/palletize 为 false 走固定点位。同一个 grasp/start 服务,两种用法——这就是"双模式"在接口层的落点。04实验目标
- 掌握A/C/D/E/F 五系列接口的定义位置、消息类型与方向(谁发布、谁订阅、谁调用)
- 理解"意图 → 任务 → 执行"调用链:intent 话题 → start_load 服务 → grasp/start 服务 → grasp/status 话题 → speak 话题,能画完整闭环
- 掌握
config/eai_factory.yaml参数配置(mode / grasp.tasks / safety / conveyor / intents),会解释意图映射"改 yaml 不改代码"的原理 - 独立完成一份接口清单表(编号/名称/类型/方向/消息/实现位置/行号/一致性),可用 ros2 命令验证(可选)
- 迁移:对任意 ROS2 项目快速产出"接口清单 + 消息类型 + 实现位置"对照表
05实验环境与准备
| 类别 | 内容 |
|---|---|
| 硬件 | B2 底盘 + C1 机械臂(可纯文档阅读,也可在已启动系统中验证);传感器:激光雷达、RGB-D 相机、光电传感器、AprilTag(tag_id=3) |
| OS / ROS | Ubuntu 22.04 + ROS 2 Humble |
| Python | 3.10(voice/vision/grasp/conveyor 运行时用 /home/iflytek/ros2_venv/bin/python3) |
| 核心库 | rclpy、std_msgs、std_srvs、geometry_msgs、sensor_msgs、car1_base_driver_msgs(SetBusServoState / MotorsState)、tf2_ros |
| 阅读对象 | docs/02_架构设计.md 接口定义 v1 章节(L26-L81)、config/eai_factory.yaml、五节点的 node.py 源码 |
mode: debug(视觉抓取会停在 await confirm 等人工确认)。改 yaml 坐标需重启对应节点生效,切勿在机械臂运动中改。06动画讲解:从意图到播报的闭环链
跟着光点走一遍完整闭环:虚线 = 话题(异步),实线 = 服务(同步必回执)。注意链路上两处"触发→完成"的两段式:start_load/grasp/start 都是受理即返回,真正完成靠 status 话题回传,最后才经 A2 speak 收口播报。
07互动讲解:接口契约选择台
给一条业务需求,你选哪种接口契约?点击选择查看评审结论:
08实操步骤:阅读、核联、验证三板斧
# ① 五节点的订阅/发布/服务/客户端实现位置(一把抓) grep -n "create_publisher\|create_subscription\|create_service\|create_client" eai_factory/*.py # voice_node.py L250-L253: A1 intent / A3 interrupt / A2 speak # safety_node.py L56-L63: C1/C2/C3 发布 + C4 客户端 # conveyor_node.py L109-L111: F3 Trigger 服务端 # scheduler_node.py L62-L67: E1-E3 服务端注册 # ② 运行时验证(可选,需系统已启动) ros2 node list | grep eai_factory # 应看到 5 个节点 ros2 topic list | grep eai_factory # intent/speak/interrupt/danger/... ros2 service list | grep eai_factory # start_load/start_sort/start_palletize/grasp/start/... ros2 interface show std_srvs/srv/Trigger # bool success + string message ros2 interface show geometry_msgs/msg/PoseStamped # ③ 手动触发装货(须安全环境!Trigger 请求体为空 {}) ros2 service call /eai_factory/start_load std_srvs/srv/Trigger "{}" # response: success: True / message: 'load task triggered'
/visual_grasp/start,但当前实现是 N-grasp 自提供 /eai_factory/grasp/start(grasp_node.py L140-L142),调度器也调用后者(scheduler_node.py L55-L56)——版本演进留下的差异,清单表应标注"以代码为准:当前实现自包含,未使用 visual_grasp 原节点"。09重点与易错点
| ⚠️ 常见坑 | 现象 → 解法 |
|---|---|
| service call 报 not available | 对应节点未启动或服务名拼错 → 先 ros2 service list 查存在性;E1-E3 由调度器提供、F1-F5 由 conveyor 提供、grasp/start 由 grasp 提供 |
| 改了 yaml 坐标不生效 | 参数在节点启动时读取一次 → 修改后重启对应节点;运行中可 ros2 param get /eai_factory_grasp grasp.tasks.load.observe_pose 检查当前值 |
| 以为 mode 只影响抓取 | mode(yaml L7)由 grasp 节点读取:debug 模式检测到目标后等 /eai_factory/grasp/confirm 人工确认(grasp_node.py L295-L301),production 直接 pick;改 mode 需重启 |
| 清单漏掉完成语义接口 | grasp/status、conveyor/state 漏记 → 漏掉它们就解释不了"装货完成"播报从哪来(闭环最后一环是 grasp/status → speak) |
| D 系列当自家接口 | docs L58 注明"D 系列为复用现有接口" → 实现在 car1_arm_controller / car1_base_driver 等复用包,eai_factory 只调用不改 |
| sort.place_pose 数值对不上 | 文档 D9 示例与 yaml 现值有差异(如 [0.1334,-0.1396,...] → [0.1434,-0.1296,0.2075,2.7283])→ 正是"配置驱动、改配置不动代码"的活例,以 yaml 实际值为准 |
- 接口清单表覆盖 A1-A3 / C1-C4 / D1-D7 / E1-E3 / F1-F10,每条含类型/方向/消息/语义/实现行号
- 能默画 语音→intent→start_load→grasp/start→grasp/status→speak 闭环
- 能指出 yaml 五段(mode/grasp.tasks/safety/conveyor/intents)的位置与作用
- 能解释意图映射两种消费方式:voice 读 intent_hints 做提示词(L228)、调度器路由服务(L62-L67)
- (可选)验证记录含 success: True / 'load task triggered' 返回截图或抄录
10实验总结与思考
覆盖五系列的清单表(含消息/语义/实现行号/一致性);"意图→任务→执行→播报"调用链图;yaml 五段解读;(可选)ros2 验证记录。
状态 String/布尔 Bool/触发 Trigger/位姿 PoseStamped 的选型规律;触发 vs 完成的两段式;表驱动意图映射。
grep create_* 抓实现位置、interface show 看结构、service call 手动触发;以代码为准标注差异。
清单表 40、调用链与实现核对 30、yaml 解读 20、验证记录与规范 10,合计 100。