← reports index
03-software/brain · 读代码画出来 · 2026-09-11

Ville 的 Bartender 链路:进程、状态机、抓取的每一步

一个人走到吧台前,机器人打招呼、听单、确认,然后用左臂 SmolVLA 模型把杯子拿起来递过去。下面三张图分别是:哪些进程在跑、状态怎么流转、真正动手臂的那一段每一步在干什么,以及 09-10 是在哪几步上出的问题。

来源:brain/PIPELINE.md · RUNBOOK.md · components.py · state_machine.py · robot_effectors.py · live_brain.py · sources.py · scripts/run_policy_trials.py · 05-training/trials/*.json 与 trace/录像

一、进程与数据流

三个 Python 环境、四个进程。虚线是读,实线是命令。橙色那一条是唯一会让手臂动的路。

策略进程自己开 3 路相机、直接写两条舵机链 硬件 头相机 OrbbecUSB3 · 424×240 左右腕相机 ×2同一条 USB2 · MJPG 17 个舵机 · 两条串口链/dev/ttyACM* · 一次只能一个进程 麦克风 / 喇叭Jabra 蓝牙 GPU (Orin)只有 train310 能用 常驻服务 robot_status.service · :8770 占着头相机 → /api/snapshot/head · /api/status(夹爪读数) → /dev/shm/xlerobot_headcam.jpg(旁路录像与关键帧只读这份) 大脑 · conversational_ai/.venv live_brain.py --send (Ville 实际用的是这个;run_brain.py 是它的离线/模拟版) FaceSource 读 8770 快照 MediaPipe 找脸 → face_seen / lost SpeechSource 麦克风 → Whisper order_parse 解析 → utterance state_machine IDLE → … → HOME LLM persona 写台词 Piper 念出来 GripSource 读 8770 /api/status grip_gate 判空/非空 → grasp_ok / failed / taken RobotEffectors speak · head · home pick · present · place 全部走子进程 HTTP,每 tick 音频 大脑拉起的子进程 head_aim.py lerobot 环境 预设 cup-grasp-v0,保持力矩 arm_home.py --arm left lerobot 环境 · 每次 pick 之前 09-10 只归左臂 · 09-11 改为 both,待真机验 run_policy_trials.py(train310 · CUDA) --model 钉死的 left-sd-p0.2-b16-u20000 @ 020000 --task "Pick up the cup with the left arm." --keep-torque --send WarmPolicy:起一次 --trials 99,模型常驻,每次 pick 发一个换行 里面:自己开 3 路相机 · 自己接舵机 · 每帧过 PolicySafetyFilter 时长上限用默认 25 s · 结果一律标 void(成败由 grip_gate 判) pick() 橙色环回:策略进程直接开腕相机与舵机链,所以 pick 期间大脑必须 release_bus,pick 完再 acquire_bus;两边同时握串口就会出 "Incorrect status packet"。
大脑自己不碰硬件:看脸、读夹爪都从 8770 的 HTTP 接口拿,动头、归位、抓取都是子进程。只有橙色那条路会让手臂动,而且它用的是另一个 Python 环境,因为 lerobot 环境的 torch 没有 CUDA。

二、状态机:一次服务怎么走

IDLE等人 GREETING30 s 没点单→IDLE CONFIRMING15 s 不答→GREETING PICKING上限 150 s HANDING等 45 s HOME归位 看到脸 听到单 说「是」★ grasp_ok taken grasp_failed → 先 arm_home 再抓,最多 3 次 APOLOGISE 三次都没抓到 → 道歉 → HOME PLACING_DOWN 45 s 没人接 → 放回桌上 → HOME (place_down 目前是空函数) ★ 唯一能让手臂动的闸: 解析出的订单只是提议,人说「是」才进 PICKING grasp_ok / grasp_failed 不是策略进程返回的,是 pick 结束后大脑读一次夹爪位置、按 grip_gate 的阈值判出来的。
主链从左到右六个状态。PICKING 是唯一有硬件动作的状态,自带三次重试;HANDING 靠夹爪位置突然变宽(有人拿走)来判 taken。

三、PICKING 里面:一次抓取的每一步,以及 09-10 卡在哪

头对准cup-grasp-v0pan -3.7 / tilt 50.1 让出串口release_bus 左臂归位arm_home left示范起始位 钉住右臂idle_lock按当前实测位 安全层两条臂都做 FK15 帧 HELD→中止 策略跑11–12 Hz · chunk 1/1上限 25 s 退出保持力矩--keep-torque否则杯子掉 读夹爪位置 ≤ 8 → 失败否则算抓到 15:09–15:30 · 5 次连起步都没有 07:52 08:03 17:27 17:31 · 4 次都够到杯子,闭爪时把杯子打飞 判定本身也弱 右臂被 14:44 的右臂测试留在桌上,钉住它 = 每帧提交一个越界姿态。 Ville 已加修复(未提交):当前位越界就不钉。 闭爪时腕高 −0.04~−0.05 m,和 09-09 成功那批一样,不是高了。 闭爪电流偏高(稳态 18–37,成功批 1–23)。原因还没定。 grip_gate 只校准了「空爪」这一档:闭合位置高于 8 就当抓到了。 杯子被弹走但爪子停得高,会被误判成功。
红色是 09-10 实测到的三处问题。第一处已有修复;第二处是这次跑不好的主体,机制看清了、原因未定;第三处是判定盲区,还没在真机上暴露。

「腕高 −0.030 低于地板 −0.016,是不是标定错了」。地板是按爪子俯仰角算的。−0.016 这个值只有在模型认为右爪朝下约 36°时才成立,此时它算出爪尖在桌面下 1.4 cm。可 15:09 的头相机照片里右臂是正常收着的,指头朝上。安全层自检里还记着训练集里右臂停着的一帧同样算出「俯仰 +52°、腕高 −0.029」并被拦下(policy_safety.py:702)。右臂停着不动的读数一直被 FK 算成穿桌,这不可能是物理事实。所以这个怀疑成立:右臂的关节标定、或者 FK 对右臂的俯仰约定,至少有一个和左臂不一致。要在真机上验:把左右臂摆到镜像的同一姿势,读关节角比一比。09-10 右臂自己跑策略没被拦,是因为它抓杯时算出的俯仰接近 0,地板退到 −0.105,够宽。

「以前跑的时候是左臂 home、右臂伸直」—— 为什么这次没先把右臂伸直。以前(09-09 那批 75%)没有任何一步去动右臂:模型对右臂 6 维输出恒为 0,这个 0 直接发到电机,右臂在开头几秒被拖成水平伸直(FK 上 z = +0.118,合法),也就是你记得的那个姿势。这是 components.py 里记作 IDLE_ARM_SWING 的「不请自动 29°」问题。09-10 Ville 加了 idle_lock 把右臂钉在当前位置以消掉这个甩动,代价就是右臂在哪它就在哪:这次刚好停在一个 FK 认为越界的位置。两个版本都没有「先把右臂放到一个已知合法的姿势」这一步。2026-09-11 已改:robot_effectors.arm_home 改成 --arm both,右臂先回 0826 数据集的示范起点(FK 算出 z +0.005 m、俯仰 −1.6°,在包络内),再由 idle_lock 钉住。check.py 全部通过,真机没跑过,第一次 --send 要有人盯着。

四、各环节现在什么状态

真机验过写好了,真机没验已知问题空函数 / 占位
环节实现状态依据
抓杯策略left-sd-p0.2-b16-u20000 @ 020000真机09-09 四场 9 成 / 3 败 / 3 void → 75%;全天 28 次起动是 17 / 5 / 6
退出保持力矩--keep-torque真机verify_keep_torque.py 在左爪上测过,位置 2803→2803
头部预设head_aim.py --preset cup-grasp-v0真机09-10 两个预设都发过并读回
左臂归位arm_home.py --arm left真机按墙钟插值,负载下也能到
右臂处理idle_lock(未提交)已改未验钉住越界姿态导致 5 次中止;不钉则右臂会甩约 29°。09-11 起 pick 前两臂都归位,待真机验
抓到没有grip_gate MEASURED_PARTIAL半校准只测了空爪 0.58;「拿着杯子」的档 MEASURED_CUP 写了但 live_brain 没接
模型常驻WarmPolicy · --trials 99真机省掉每次 23 s 加载;09-10 的 4 次 pick 走的就是它
看脸 / 听单 / 说话sources.py · Whisper · Piper真机15:00 有三段 brain_say 音频落盘
递杯present()空只写日志;face_reach 只能指向脸,够不到人
放回桌上place_down()空place_pose.json 有左臂姿态,函数没接
桌上有没有杯子cup presence恒真决定性地 stub 掉;EfficientDet 0 框
语音识别挂掉时—无能打招呼道歉,接不了单,没有按钮兜底

五、怎么跑,以及换模型要动哪

PY=~/miniconda3/envs/lerobot/bin/python
cd ~/Robotic_challenge/03-software/brain
$PY check.py                          # 13 个模块自检 + components --verify
$PY run_brain.py --simulate           # 假机器人走完整段
$PY ../scripts/teach_grasp.py --check # 舵机 / 杯子 / USB
$PY run_brain.py --send               # 真机(Ville 实际用 live_brain.py --send)

换一个左臂 checkpoint

改 components.py 第 80–81 行的 path;robot_effectors.py 第 422 行自检写死了 left-sd-p0.2,要一起改。起始位和头部预设按数据集来。

换成右臂模型

不是换路径是换臂:任务句子在 drinks.py / live_brain.py / live_cycle.py 三处写死「left arm」,arm_home 写死 left,place_pose 只有左臂,右臂 FK 地板还有上面那个疑点。

六、证据等级

结论等级出处
进程、端口、子进程命令行读代码live_brain.py:371 · robot_effectors.py:318 · sources.py:42
5 次中止的原因跑测过trials-live.jsonl 的 aborted 字段 · run_policy_trials.py 未提交 diff 的注释
4 次 pick 都在闭爪时把杯子打飞跑测过vid-20260910-{075224,080307,172712,173032}-t1.webm 逐帧 · trace FK
打飞的原因没定需要同杯位、60 s、手跑 run_policy_trials 对照
右臂 FK / 标定不一致推断15:09 照片 + policy_safety.py:702 自检记录;要真机比关节角
09-09 关键帧颜色反了已核trial_keyframes.py:151 于 09-10 加了 RGB→BGR;录像颜色正常,硬件没换

本页数字实测于 2026-09-10 本机 Jetson;页面写于 2026-09-11。artifact 版本:claude.ai。