Kindle做面板:24小时插电的充电控制实现
Kindle 信息面板Kindle 当面板,24 小时插着电——这次要解决的是电池的事。
写完 Kindle 信息面板,我就在想:
“这么一直插着电,电池会有事的吧?”
我回去一查,还真是。
先看我的面板是怎么工作的
这台 Kindle 现在的状态是:横躺在显示器旁边,接着充电器,24 小时亮着。
它同时在做三件事:
面板常驻运行,持续阻止系统自动屏保(否则几分钟就黑屏,还怎么当面板)
每 30 分钟联网取一次数据,热点每 5 分钟切一条
插着充电器,一直在充电
前两件事是设计意图,第三件事是副作用。
而这个副作用,对锂电池是最不友好的组合:长期挂在 100% + 充电时持续发热。手机和笔记本早就把这件事写进产品逻辑里了——充到 80% 停充,是厂商替用户操的心。Kindle 当面板没有这套逻辑,于是电量和充电开关会一直顶在最上面。
一年下来,这块电池就一直在"满着"的状态里泡着。
所以这篇要解决的,就一件事:让它别一直满着。
目标:把电量锁在 40%–80% 之间
思路很直接:电量涨到 80% 就请求停充,掉到 40% 再恢复充电。
条件 行为 电量达到或超过 80% 进入停充阶段;接入电源时请求关闭充电 停充阶段,电量在 41%–79% 继续等待;拔插电源不会重置阶段 电量降至 40% 或以下 进入充电阶段;接入电源时请求开启充电 充电阶段,电量在 41%–79% 保持充电,直到再次触达上限 启动时电量在 41%–79% 默认进入停充阶段,等待电量降至 40%
中间那 40 个百分点不是浪费,是滞回区间:如果只有一个阈值,电池就会在 80% 附近反复启停充电,电流一开一关反而更伤。留一段缓冲区,充停各一次,比来回折腾强。
几个实现上的选择,我觉得挺讲究:
限充和显示面板是两个独立进程。 停掉面板不会停掉限充——面板是"看",限充是"养",两件事不该互相绑架。停止限充要用单独的菜单项。
每 10 秒检查一次。 读电量、读外部供电状态、读充电开关,再决定要不要动作。不快不慢,功耗也可忽略。
插上电的瞬间可能先充一下再停。 因为系统会先响应"电源已接入",控制器在下一次检查时才纠正它。这个延迟是系统层面的,躲不开,也不影响结果。
启动时默认进入停充阶段。 哪怕你是在 60% 的时候启动的,它也会先等你掉到 40% 再开始充——宁可少充,不可多充。
第一步:这个开关就摊在 sysfs 里
Kindle 越狱之后,底层就是个 Linux。电池和充电状态不需要任何逆向,直接就在 sysfs 里:
接口 用途 /sys/class/power_supply/bd7181x_bat/capacity 读电池百分比 /sys/class/power_supply/bd7181x_ac/online 读外部电源是否接入 /sys/class/power_supply/bd7181x_bat/charging 读充电状态,写入 0 或 1 可请求停充 / 恢复充电
关键就在第三条:这个属性是能写的。
能写,意味着内核驱动给这个属性注册了写函数——写进去 0,充电真的会停下来。
第二步:读固件源码,确认"写 0"到底意味着什么
属性可写,只说明"能写",不说明"写了会怎样"。这中间隔着语义,得把它问清楚。
这台 Kindle 的固件是 5.16.2.1.1,对应的 GPL 源码包在 Kindle_src_5.16.2.1.1_4097490002.tar.gz 里。解开之后,充电驱动在 drivers/power/bd7181x-power.c——ROHM BD71815/BD71817 的充电驱动。核心逻辑就四行:
if (val == 1)
bd7181x_set_bits(pwr->mfd, BD7181X_REG_CHG_SET1, CHG_EN);
else
bd7181x_clear_bits(pwr->mfd, BD7181X_REG_CHG_SET1, CHG_EN);
写 1 置上 CHG_EN 位(允许充电),写 0 清掉它(停止充电)。
到此为止,"写 0 = 停充"这句话才有了依据,而不是猜的。
📌 必须说明白:这是读源码 + 实机验证,不是对固件二进制做反编译。我解出来的驱动文件和此前分析留存的副本 SHA-256 完全一致(
9f261cb9…7f41ab)。但这个包的官方下载地址和官方校验值我没有留存,来源以及它与设备正在运行的内核是否逐字对应,也没有独立验证过。愿意较真的话,这一层请自行核对。
源码还顺手告诉我两件"不太好看"的事
第一,写入成功 ≠ 寄存器真的改了。 这个驱动的写函数在解析到非法输入时仍然返回写入字节数,而且根本不检查底层 set_bits / clear_bits 的返回值。也就是说,echo 0 > charging 返回成功,并不能单独证明寄存器被改动了。
那怎么办?控制器在接电状态下写完之后会立刻回读 charging 做二次确认,读到不一致就报错退出——但也请明白,回读同样不能单独证明实际充电电流或硬件供电路径发生了什么。这是能拿到的最好证据,不是铁证。
第二,拔掉电源时 charging 读数是 0,这不能证明充电开关被禁用了。 因为这个读数的语义是"充电器在线 且 CHG_EN 位置位"——电源都没插,两个条件里的第一个就不成立。所以控制器必须同时读外部电源状态,不能只看这一个数。
这种"知道自己的证据链到哪儿为止"的诚实,我觉得比一个漂亮的结论更值钱。
第三步:写一个"自己会恢复"的探测脚本
到这儿已经知道怎么停充了。但别急着写长期运行的控制器——先写个一次性的探测脚本,专门用来在实机上确认"写 0 真的会停"。
这个脚本的设计是整套实现里最值得抄的部分:
# 前置条件:不满足就直接退出,绝不瞎写
test "$(cat "$AC")" = 1
test "$(cat "$CHARGING")" = 1
# 任何退出路径都恢复充电
trap restore EXIT
trap 'exit 1' INT TERM HUP
# 记录 before → 写 0 → 等 8 秒 → 记录 → 断言
printf '0n' > "$CHARGING"; sleep 8
test "$(cat "$CHARGING")" = 0
# 恢复 → 记录 → 断言
printf '1n' > "$CHARGING"; sleep 8
test "$(cat "$CHARGING")" = 1
四个要点,一个都不能少:
前置断言:没插电、当前没在充电,就直接退出,绝不碰硬件
EXIT trap:脚本被中断、被 kill、报错退出,充电都会恢复
写完要等:读数有延迟,写完立刻读可能读到旧值(这里等 8 秒)
写完要断言:读回来和预期不符就报错退出
注意它会真的动硬件,不是只读诊断。它属于"探测工具",跑完就退出,不常驻。
第四步:把策略从硬件里剥出来
第四步决定了这套东西好不好维护:策略必须是纯函数,硬件只负责执行。
decide(capacity, online, charging, phase) → "action phase"
action 只有三种:stop(写 0 停充)、start(写 1 续充)、restore(写 1 恢复);phase 是内部状态 hold(停充阶段)或 charge(充电阶段)。判定逻辑就几行:
[ "$capacity" -ge 80 ] && phase=hold
[ "$capacity" -le 40 ] && phase=charge
好处立刻就来了——这个函数可以在电脑上穷举测试,不用碰硬件。我给它写了个对拍脚本:
for percent in range(101):
for state in (0, 1):
for phase in ('hold', 'charge'):
# 101 × 2 × 2 = 404 组组合,逐组和参考实现比对
除了 404 组正常组合,还要测三类边界:非法输入必须失败(电量 101、在线状态 2、阶段 bad)、拔插电源不能重置阶段(75% 拔电 → restore 但阶段仍是 hold)、跨阈值的完整状态序列(80 → 75 → 74 → 41 → 40 → 60 → 拔电 → 60 → 80)。
这种"同一套逻辑写两遍、让它们互相验证"的做法,比单纯写单测更可靠——Python 那份是参考实现,Shell 那份是实际跑的代码,两边必须一模一样。
第五步:给它配个看门狗
写这种"后台常驻 + 修改硬件状态"的脚本,最怕的是它自己悄悄死掉,然后设备就卡在一个奇怪的状态上——充电开关一直关着,人还蒙在鼓里。
所以控制器拆成了两个进程:
监控进程:干活的那个,每 10 秒更新一次心跳
看护进程:独立运行,每 15 秒查一次,发现监控进程没了、或者心跳超过 180 秒没更新、或者时间戳异常超前,就请求恢复充电并记日志
主动停止限充时,也会先尝试恢复充电再退出。
日志长这样:
charge limiter started: stop at 80%, resume at 40%
charging disabled at 85%
charging enabled at 39%
watchdog: monitor stopped or timed out; restoring charging
实机验证过的场景有三个:高电量下停充生效、手动停止后请求恢复充电、监控进程被强制杀掉后看门狗接管并恢复充电。
但有一个关键场景我还没验证:从停充状态一路放电到 40% 以下、自动续充、再充满回到 80% 的完整闭环。 策略逻辑写了单元测试(test-charge-policy.py,纯逻辑,不碰硬件),逻辑上没问题——但测试通过不等于实机跑通,这一条我不敢替它打包票。等它真跑完一个周期,我再回来更新。
这套东西是怎么写出来的:AI 工作台的实际分工
先说清楚结论:AI 在这个项目里省下的是"查证"的时间,不是"负责"的时间。
我这套是 WorkBuddy 和 opencode 一起扛的——opencode 负责项目级的脚本改动和命令执行(项目里 opencode.json 直接配了 .agents/skills 技能目录),WorkBuddy 负责读文档、跨文件改代码、以及把每个改动同步进文档。
具体分工是这样的:
① 定位驱动源码 —— AI 省的是"检索"的时间。
300MB 的内核源码包解开后,充电驱动在哪个文件、charging 属性对应哪个 store 函数、寄存器位定义在哪——AI 能顺着 power_supply 类、DEVICE_ATTR、supply 属性注册这几条线索快速定位候选文件,我只需要逐行核对它给的语义对不对。
从"翻目录"变成"审答案",这个差别很关键。
② 写探测脚本 —— AI 出草稿,人补安全约束。
脚本骨架 AI 一口气就能给出,但真正救命的四样东西——前置断言、EXIT trap、等待时间、写后断言——必须是我自己想清楚再补上的。这部分 AI 不会替你负责,因为它不知道这块硬件写下去会发生什么。
③ 生成穷举测试 —— 这个最省事。
把 decide 的语义讲清楚,让 AI 生成 404 组对拍用例、非法输入用例和状态序列用例,人来跑、来审。这是典型的"人给规格、机器出例",也是收益最明显的一步。
④ 记录与同步 —— 适合交给 AI。
每次改动都要同步进 README.md 和限充说明文档,包括"哪些验证过、哪些没验证过"。AI 干这活儿又快又不会漏格式。
但更值钱的那句话是:让 AI 干,不要让 AI 判
这轮下来我最深的体会是:AI 把"我不会用这颗芯片"变成了"我不确定这么干安不安全"。
前者的答案是"去查",后者的答案是"想清楚"。而这恰好是对的——因为限充这种事,真正会出事的地方从来不是代码写错,而是代码写对了但语义理解错了。
所以有几件事我坚持不让 AI 代劳:
"已验证"三个字,必须来自实机测试。 文档里那句"完整放电—续充周期尚未实机验证",AI 写得出来,但它没法替我确认。
事实性描述要自己核对。 源码包从哪来、有没有官方校验值,AI 只能引用我给它的信息。
危险动作必须有人工设计的兜底。 trap、看门狗、启动前置断言——这些不是"提示词写得好"就能有的东西。
写代码可以交给 AI,写硬件不可以。
跑起来之后长什么样
限充做完后,我在面板上加了个设备页,电量、供电情况、限充状态一眼就能看到,不用 SSH 也知道自己这台设备现在什么状态:
Kindle 设备状态页设备页:电量 90%、供电未接电、限充未运行、无线已启用,四个热点来源全部正常
这张图拍的时候限充还没启动,所以显示"未运行"。限充跑起来后这一栏会跟着变,停充中、充电中一目了然,切换的瞬间日志里也会记一笔。
日常页的底栏也常驻显示电量和供电状态:
Kindle 日常页与底栏状态日常页:农历宜忌、节气、天气与空气质量、穿衣建议、四列热搜、今日一句;底栏是"更新 14:25 电量 83% 未接电"
用起来是这样
KUAL → Lunar Panel → Start charge limit 40-80,控制器就起来了。这一步不需要先启动显示面板,限充是独立的。

要关掉限充并恢复普通充电,选 Stop charge limit and enable charging——记住它和 Stop panel 是两个不同的操作,一个停显示,一个停限充。
日常检查用 SSH 只读命令就够了:
cat /sys/class/power_supply/bd7181x_bat/capacity
cat /sys/class/power_supply/bd7181x_ac/online
cat /sys/class/power_supply/bd7181x_bat/charging
tail -n 30 /mnt/us/extensions/LunarPanel/charge.log
日常使用上,你要察觉不到它的存在才对:电充满到 80% 就自己停,掉到 40% 又自己充,像什么都没发生一样。
边界,必须说在前面
只在 Kindle 基础版第八代(Basic 2 / Eanab)、固件 5.16.2.1.1 上实机验证过。 依赖这块设备的
bd7181x驱动接口,其它型号和固件不能因为接口同名就假定兼容。完整放电—续充周期尚未实机验证。 策略测试通过不能替代实机跑通。
充电阶段只存在运行中的进程内存里,停止或重启控制器就重新初始化,跨重启不保留。
不会开机自动启动。 设备重启后要回 KUAL 再点一次 Start。
休眠、断电、强制重启、系统故障时,不保证控制器和看护进程能及时恢复充电。
停充不会强制放电,也不保证插电时电量快速下降——设备报告的
Discharging也不代表外部电源和电池各承担了多少负载。这不是电池硬件保护机制的替代品。 它只是把"长期满电停留"这件事变少了一点。
至于"到底能延长多少寿命"——我不能保证具体提升。实际效果受电池状态、温度和使用条件影响。
另外一条给自己也给你的提醒:充电控制这类操作,请只在自己完全理解、能承受后果的设备上做。 写错了大不了充电开关一直开着,最坏情况也只是电池少用几年;改错了固件,那才是真的麻烦。
如果你也想针对自己的kindle实现,可以交给AI来做,毕竟它是专业的。
