解读 07 · model.rs 与 io.rs:机器人描述与硬件 IO
解读对象:
duck-control/src/model.rs(225 行)、duck-control/src/io.rs(335 行)
需要的前置:《什么鸭公开课》第 04 课(61→14 契约);总线背景见解读 01
1. 机器人是一组表,不是一个类
model.rs 的模块注释只有一句话:"The robot, as data"(model.rs:1)。这台鸭子只有一个版本 alpha,没有类型层级,只有几张并列的表:NUM_JOINTS = 15(model.rs:12);JOINT_IDS 按"左腿 5 · 颈头嘴 5 · 右腿 5"排列——Dynamixel ID 20–24、30–34、10–14(model.rs:15-19);DEFAULT_POSITION 是 15 个弧度值的 home 位(model.rs:39-55)。
表会漂移,const 块断言(const 块是编译期求值的代码,断言失败即编译失败)负责钉死:JOINT_NAMES.len() == NUM_JOINTS(model.rs:26)。JOINT_NAMES 从协议 crate pub use 再导出——总线上按数组下标索引关节,名字和顺序不许有两份副本。测试再补一层:ids_are_unique(ID 重复的症状"像接线故障",model.rs:144-153)、home_pose_legs_are_mirrored 检查左右腿 home 位互为相反数(model.rs:210-224)——符号写错肉眼看不见,只会让鸭子站歪。
嘴是唯一的特例:MOUTH_INDEX = 9(model.rs:31),不在任何 14 动作策略里。控制嘴的全部代码是 MOUTH_CLOSED(-5°)、MOUTH_OPEN(+30°)和 mouth_target(model.rs:62-74)——开合比例先钳到 0..1 再插值,NaN 归零,坏数值不会变成超程指令。
2. 数值来自实测,不来自数据手册
model.rs:7-9 的注释值得抄下来:数值来自 microduck_runtime 的 motor.rs,是"在硬件上量出来的",从数据手册重推"恰恰是那种看起来对、走起来错的改动"。两个例子:
EXPECTED_REGISTERS(model.rs:89-94)是启动时逐台校验的 EEPROM 寄存器,其中return_delay_time = 0是承重墙:XL330 出厂值 250 意味着每台应答多等 500 µs,16 台一拍就是 8 ms——20 ms 预算的 40% 花在等舵机"想起来回答"。- 电池没有电量计、没有 ADC,唯一测量是舵机上报的自身供电,经总线"带负载下坠、静止回升"。所以
BATTERY_FULL_V = 8.2、BATTERY_EMPTY_V = 6.6(model.rs:109、113)定义的是"带负载可用"区间而非电芯化学区间;battery_percent(model.rs:124)对非有限或非正读数一律返回 0——那是"总线没回答",调用方该报"未知"而不是"0%"。
3. io.rs:让控制循环可以没有鸭子的接缝
模块注释点明意图(io.rs:1-4):RobotIo::read 和 write 之间的一切都是纯计算——trait(特征,Rust 的接口)是把硬件挡在门外的那道缝。Sensors(io.rs:16)把关节和 IMU 打包成"一次原子采样":IMU 板挂在 Dynamixel 总线上,和舵机同一笔事务读回,拆成两个 trait"反而发明了总线没有的区别"(io.rs:6-9)。
trait 方法各带理由:set_gain 在这里是因为"瘫软"必须有意义——拒绝指挥摔倒的机器人只是把它冻在原姿势里,降 P 增益(原型 kP 50 对运行值 200)才让它卸力(io.rs:119-124);set_torque 的注释大写强调"启动时没有任何东西调用它"(io.rs:132-134),更新器重启守护进程不能让站着的鸭子动一下。SlowSensors(io.rs:81)单独一个方法:电压和逐关节温度约每秒一采,不走 20 ms 关键路径——温度要逐关节不是取平均,"蹲着受力的膝盖比嘴热得多",平均值恰好藏起那个即将过热锁存的舵机(io.rs:86-89)。
另一半是 FakeIo(io.rs:166):永远编译进 crate,cargo test 因此不需要硬件。字段按"要能表达哪类测试"设计:fail_reads 模拟"板子先起、舵机后上电",frozen() 模拟被手推的瘸腿机器人,torque: Option<bool> 的 None 是"重启没让机器人动过"的样子(io.rs:187-193)。
你带走的收获
- 描述机器人用数据不用类;表间不变量交给编译期断言加针对性测试,不靠自觉。
- 实测参数写明出处,并明说"不要从手册重推"——重推出的正确值可能是错的。
- 没有燃料计的电量:定义"带负载可用"区间,把"没读到"和"读到 0"分成两种回答。
- IO trait 按循环要表达的动作定方法;测试替身能注入失败、能冻结、能计数,错误路径才测得到。
延伸
- 这些表怎么被 61 维观测消费:解读 02
- 本地路径:
/Volumes/dev/dev/microduck/duck-control/src/model.rs、/Volumes/dev/dev/microduck/duck-control/src/io.rs