解读 09 · robotd/main.rs + control.rs:守护进程的控制循环
解读对象:
robotd/src/main.rs(7561 行,本篇读主干)、robotd/src/control.rs(706 行)
需要的前置:解读 04(安全层)、解读 03(ONNX 策略)
1. 健康度决定了形状
main.rs 头 20 行先讲"为什么长这样"(main.rs:6-10):更新器的自动回滚只在 robot.health 有意义时才有意义,而它一度只意味着"循环跑过一拍"。现在健康意味着循环在赶上周期:"一个跑在目标 60% 的循环活着、应答每个请求,而且坏得很彻底"。第二条原则(main.rs:16-19):每个方法都必须在机器人状态很糟时可以应答,所以 IPC 侧只读控制循环发布的原子量,从不调进循环——卡死的循环报告自己不健康,而不是把客户端挂住。
进程骨架在 main(main.rs:867):加载参数,spawn_control_thread(main.rs:1022)把控制循环放到专属 OS 线程 + current-thread 运行时上——总线读是阻塞式串口 IO,共享运行时会占住一个 worker(main.rs:1016-1021);独立运行时让 IPC 排不进一拍前面。退出时置 shutdown 并 control.join(),让循环做完手里那笔总线事务(main.rs:963-966)。
2. 一拍里的顺序
control_loop(main.rs:1553)每拍依次:读总线(失败计数,第 1 次和每第 10 次打日志,main.rs:1799-1801)→ 读失败时按 COAST_TICKS = 3(main.rs:120)沿用最后一个好样本滑行——掉一次事务是常态,实测约每分钟 8 次;过去每次掉读都停策略回 hold 位,就是用户报的"随机小抽搐"→ safety.observe → intents.snapshot → safety.gate(死手开关在这里读 twist 的年龄)→ 判定 driving(main.rs:2535-2543,七个条件:使能、Ready、有控制器、不在摔落序列、有样本、IMU 已收敛、未断电)→ controller.step → safety.apply 写回(main.rs:2834)。
节拍器:tokio::time::interval + MissedTickBehavior::Skip(main.rs:1641)。注释拿实测说话:用 Delay(每拍从"现在"重排下拍)时循环报 43.1 Hz 而目标 50 Hz、missed = 0——不是干不完活,是每次唤醒延迟都被累加进周期。
3. control.rs:只提议,不指挥
control.rs 模块注释给 Controller 划了界(control.rs:2-5):它不持有任何 IO 句柄,构造上就命令不了电机,只能产出 Step { targets, label, gain, busy }(control.rs:118)。Controller::step(control.rs:425)按写死的优先链选网络:skill > ground pick > 坐/起 > 按幅值选站立 > 走(control.rs:477),然后 targets[joint] = DEFAULT_POSITION[joint] + scale * offsets[joint](control.rs:601-604),再对头和腿做一阶低通(head_lowpass = 0.5、legs_lowpass = 0.7,训练值,control.rs:59-63)。默认调参集中在 Tuning::default(control.rs:66-77):action_scale 0.9、站立增益比 0.8、gain 200——测试 the_defaults_match_the_prototype 逐个钉住。
两处注释点名"容易好心改坏"的怪癖:踢腿窗口按站立项调参跑(踢腿观测里命令全零,原型恰好由此触发站立过渡,control.rs:24-28);窗口在用过之后才推进,"原型在电机写之后推进相位"(control.rs:623-625)。
4. 循环之外:让健康度可计算
循环每 1 秒窗口(RATE_WINDOW,main.rs:85)算一次实际频率存进 achieved_hz(main.rs:2887-2893);超期的拍计进 missed(main.rs:2883-2885)。健康判定在 RobotState:上次打卡距今超过 stall_periods 个周期 → 不健康"control loop stalled for N ms"(main.rs:778-783);频率低于下限 → 不健康(main.rs:785-791)。safe_to_restart 只在"正在走且没摔"时说不可重启——"行走中重启电机控制是摔倒的方式"(main.rs:850-858)。
你带走的收获
- 看门狗测两件事:频率是否达标、是否停跳,且由循环自己持续打卡。
- 周期定时器用"跳过错过的拍"而不是"从现在重排",否则调度延迟会静默吃掉速率。
- 控制器对象不持 IO 句柄,"只能提议目标"由构造保证;安全层独占唯一出口。
- 偶发失败(掉一次总线读)用沿用上一样本吸收——重置本身是新的不连续。
- 把"容易好心改坏"的原型怪癖写成注释,是守护 sim2real 的一部分。