解读 116 · soc.rs:电池电量监测
解读对象:
robotd/src/soc.rs(84 行,全读)
需要的前置:导览见解读 12;电量百分比映射见解读 07
这是全系列最小的一篇:一个函数加两个测试。小,恰好因为它守着一条干净的边界——"板子说自己"与"机器人说自己"是两件事(soc.rs:1-6)。板温读的是 /sys/class/thermal 的 sysfs(内核把传感器暴露成文件的接口),不碰 Dynamixel 总线,所以不进 duck-control、不走 RobotIo。注释给出真正的理由:"总线不响应时,热读数反而最有意思"——一块堵住通风口的板子,恰恰是在舵机不再应答时最值得被看见的那块(soc.rs:4-6)。
1. hottest_zone_c:签名与取最大
pub fn hottest_zone_c() -> Option<f64>
返回 Option<f64>:Rust 的"可能没有"——没有热区的机器(macOS 开发机、无 sysfs 的容器)得到 None,靠"找不到文件"降级,文件里没有一行 cfg 条件编译(soc.rs:8-10)。核心决定写在函数文档里(soc.rs:19-24):取所有热区的最大值,不是 CPU 的值。一块 Radxa Zero 3 就有 soc-thermal 和 gpu-thermal 两区,别的板子还有 NPU、DDR 区;按名字挑一个区,"第一次遇到一块传感器接线不同的板子,就会漏掉真正在升温的那区"。
2. 逐段:目录、毫度、坏读数
函数体(soc.rs:30-56)四步。每次采样都重新 read_dir(soc.rs:32-34),不缓存目录列表:1 Hz 下一次遍历只有几微秒,而缓存在"驱动加载后热区才出现"时会过期。只看名字以 thermal_zone 开头的项(soc.rs:35-38)。读 temp 文件并除以 1000(MILLI_PER_DEGREE = 1000,soc.rs:16):内核热 sysfs 的 ABI 用毫摄氏度,47 °C 在文件里是 47123。坏读数跳过而不是平均进去(soc.rs:46-50):≤0 的读数"在运行中的板子上是传感器故障,不是温度";hottest.is_none_or(|high| celsius > high)(soc.rs:51)一行同时处理"还没有最大值"与"比它大"两种情况(is_none_or 是 Option 的方法:为 None 时返回真)。
3. 谁在用它:电池是邻居,不是同一回事
调用点在 main.rs 的 publish_slow_sensors(main.rs:3376):每 1 秒(RATE_WINDOW,main.rs:104)跑一次的"慢传感器"采样,不属于 50 Hz 一拍——电压/温度寄存器是一次额外的总线事务,一秒一次约一毫秒无妨,50 Hz 就是 5% 预算(main.rs:3366-3370)。注意热读数排在总线读之前且无条件(main.rs:3382-3386):它是 sysfs 读,"欠总线什么都不欠"。电池侧在这个函数里做的是电压 EMA:BATTERY_EMA_ALPHA = 0.1(main.rs:118),约 10 秒时间常数,压住随步态起伏的电压;首读直接播种而不是从 0 混上去——"否则要花十秒报告一块比实际更瘪的电池"(main.rs:3393-3399)。关节温度不平滑:"舵机外壳已是慢信号,EMA 只会推迟大家关心的那个读数——正在爬向过热关机的那个"(main.rs:3401-3403)。百分比换算(满电压/空电压映射)住在 model.rs,见解读 07。
4. 测试:形状与标度
两个测试各守一种典型错误。a_reading_is_plausible_or_absent(soc.rs:66-75)断言"合理或缺席":macOS 上必须 None 而不是 panic,Linux CI 上读数须落在 1..=150 °C——测试不知道机器当时的温度,所以只能钉住形状。millidegrees_convert_to_degrees(soc.rs:80-83)就一行:47123.0 / 1000.0 == 47.123,注释把风险说透:"忘了除数,47 °C 变成 47000,任何后来写的'过热了吗'阈值都拦不住它"(soc.rs:77-79)。
你带走的收获
- 归属按"碰不碰总线"划:sysfs 读归板上守护进程,不进控制库——边界清晰,函数才可能只有 84 行。
- "取所有热区最大值"是保守且可迁移的答案;按名字挑区是给下一块板子埋雷。
- 读数 ≤0 当故障丢弃:
None/跳过比把坏样本平均进去诚实。 - 慢信号(电池电压)用 EMA 平滑,但平滑不是万能——外壳温度已经够慢,再平滑只剩延迟。
- 测试钉不住具体值时,钉形状(合理区间或缺席)与标度(毫度换算)。