2406 字
12 分钟
给一块深睡的墨水屏留一扇门:WiFi 恢复模式

前两轮把这块 TRMNL 墨水屏改造成了”服务端渲染、设备只当画框”的自建系统,固件深睡 15 分钟醒一次拉图。这一轮解决一个一直悬着的运维问题:WiFi 变了怎么办

设备的全部联网凭据烤在 NVS 里。搬家、换路由器、改 WiFi 密码,任何一种情况发生,设备醒来连不上网,就再也收不到新图——而且你没有任何远程手段救它。翻固件源码确认了现状:

  • 凭据存储其实已经支持最多 5 组 SSID,LRU 排列,重连时还会扫描周边按 RSSI 信号强度排序逐个试(lib/wificaptive 里的 autoConnect())。多备份本身不是缺口。
  • 但 5 组全部失败后,固件走 wifiErrorDeepSleep():按 60s → 180s → 300s → 900s 退避重试,永久循环,静默到底。不开配置入口,不提示,什么都不做。
  • 唯一的恢复手段是物理长按背面按钮:5–15 秒清空全部已存 WiFi,15 秒以上连设备注册凭据一起清。破坏性强,还得人在设备旁边精确掐时间。

也就是说这块屏一旦”失联”,就只能拆下来按按钮重配。对一块挂在墙上的显示器来说,这个恢复路径太重了。

方案:失败退避耗尽后,每次醒来开 5 分钟热点#

设计目标很明确:不清任何已存凭据,给用户一个手机就能操作的恢复入口,同时不能毁掉深睡架构的续航。

最终的状态机:

[正常运行] --全部SSID失败--> [快速重试期](60/180/300s,行为不变)
[快速重试期] --第4次仍失败--> [恢复模式]
[恢复模式] 每次唤醒:
读电量 → (必要时)重绘恢复屏 → 开热点"TRMNL" 5 分钟
├─ 有人提交新 SSID 且连上 → 存入 5 槽 LRU,回到正常流程
└─ 5 分钟无人连接 → 深睡 900s → 下次唤醒再开

几个关键取舍:

为什么等第 4 次失败才开热点——现有的 60/180/300 秒三级退避本来就是为了扛住路由器重启这类临时抖动。网络闪断一下就弹配置热点,误报率太高,还费电。第 4 次失败意味着约 9 分钟内 4 轮全灭(每轮内部还有 3 次×5 组的重试),基本可以确认不是抖动。

为什么热点限时 5 分钟——原版 startPortal() 是个 while(1) 死循环,没有任何超时机制,AP 模式常亮直到有人提交。ESP32-S3 开 SoftAP 的工作电流在 100mA 量级(datasheet 没有单独的 SoftAP 行,结合 TX/RX 拆分和社区实测的粗略数),挂一晚上电池就见底了。改成带超时参数:

lib/wificaptive/src/WifiCaptive.h
/// @param timeoutMs 0 = wait forever (original first-setup behavior);
/// >0 = give up and tear the AP down if no SSID was submitted
bool startPortal(uint32_t timeoutMs = 0);

默认参数 0 保持原行为,首次配网流程和其他板子(这份固件同时服务 ESP32-C3 原版硬件)完全不受影响。循环里的超时判断用 millis() 减法,回绕安全:

if (timeoutMs > 0 && (millis() - startMs) >= timeoutMs)
{
break; // succesfullyConnected 保持 false,走既有清理路径
}

顺手修了一个原版小问题:portal 退出时如果没人提交过 SSID,清理路径会拿空字符串当凭据再试一次重连,白等 15 秒超时。加了个 _ssid != "" 的防护。

为什么每次醒来都重开——如果只给一次机会,错过窗口就退回永久静默,跟原来一样只能物理复位。改成”只要还连不上,每 15 分钟就有 5 分钟的窗口”,用户随时找个时间拿手机连一下就行。恢复模式下电池能撑约 3 天(估算:每 20 分钟周期里 5 分钟 AP + 15 分钟深睡,平均 ~26mA,1800mAh 电池 ≈ 69 小时),足够周末出门回来再处理。

恢复屏:电量、续航估计、和”时刻表式”文案#

恢复模式的屏幕要回答用户三个问题:出什么事了、我该做什么、还剩多少时间。布局:左上电池图标 + 百分比/电压,右上帮助二维码,中下四行指引:

WiFi unreachable - recovery hotspot is ON
Join 'TRMNL' WiFi on your phone to submit a new network
Open for 5 minutes, reopens every 15 minutes until fixed
Estimated battery left in this mode: ~2.1 days

(固件字体只有拉丁字形,所以文案是英文。)

这里有两个墨水屏特有的设计点。

第一,文案必须是”时刻表式”陈述。墨水屏断电保持图像——设备深睡后屏幕上的字还在。如果写”热点已开启”,深睡期间就是错的;写成”每 15 分钟重开一次、每次 5 分钟”,任何时刻读都成立。

第二,不能每次醒来都重绘。恢复模式每 15 分钟醒一次,如果每次都全刷(黑白翻转闪几秒),既费电又刺眼。方案是把上次绘制时的电量百分比存进 NVS,电量变化不到 5% 就跳过重绘——反正屏幕内容不会变。这个去重不能用 RAM 变量做:深睡唤醒等于重启,RAM 全清,必须落 NVS。

电量换算是新写的纯函数库:1S LiPo 的电压-电量分段线性表(4.2V≈100%、3.7V≈50%、3.0V≈0%,注意这只是粗略近似,真实放电曲线中段平坦两端陡降),加一个”恢复模式续航 ETA”。纯函数意味着可以跑主机端单元测试,11 个用例覆盖端点、插值、越界 clamp 和单调性,TDD 先写测试再实现:

Terminal window
pio test -e native -f test_battery
# ================= 11 test cases: 11 succeeded =================

每次刷新叠加时间戳,和一个鬼影问题#

这轮还加了个小需求:屏幕右下角显示每次刷新的时间(MM-DD HH:MM)。动机和恢复模式同源——WiFi 断了之后屏幕内容会悄悄过期,挂着三天前的数据看起来和实时的一模一样。有了时间戳,内容陈旧一眼可见。

实现本身不复杂:NTP 同步过一次后 RTC 会跨深睡保持时间,画完帧内容后用 strftime 格式化、白底黑字盖到右下角。时钟从没同步过就跳过(判断 tm_year)。时区通过 configTime 的 gmtOffset 参数设置,epoch 和遥测不受影响。

有意思的是它跟上一轮做的 partial refresh 撞出了一个一致性问题。

回顾一下:这块屏每次醒来做局部刷新时,要先把上一帧从 SPIFFS 的 /last.bmp 读回来、写进墨水屏控制器的”旧帧平面”,控制器拿新旧两帧做差分,只驱动变化的像素。差分的前提是旧帧平面和面板物理显示完全一致。

原来的代码顺序是:下载图片 → 存 /current.bmp → 显示。时间戳是显示阶段才盖进显存的,也就是说存盘的文件没有时间戳、屏幕上有。下一次醒来,/last.bmp(无戳)被当作旧帧参照,而面板上实际印着上次的时间戳——差分在时间戳区域拿到错误的参照:面板是黑字,旧帧平面以为是白底,新帧那里也是白底,差分认为”没变化、不驱动”,结果上一次的时间戳作为鬼影留在屏幕上,要等每 8 次一遇的强制全刷才被清掉。对一个每 15 分钟就变一次的时间戳来说,屏幕角落会常驻两三个叠影。

修法是让存盘的文件严格等于面板内容:

  1. 显示函数把时间戳盖进 buffer 后,把 buffer 翻转回 BMP 原始的 bottom-up 朝向(显示前有一次垂直翻转,翻转是自逆操作,再翻一次就还原了,只是这次戳已经嵌在里面);
  2. /current.bmp 的存盘从”显示前”挪到”显示后”。

这样下一轮 /last.bmp 读出来、翻转、写入旧帧平面的内容,和面板物理状态逐像素一致,时间戳区域的差分就能正确驱动——旧戳被新戳干净地替换。

这类问题的一般教训:在”显示内容会被持久化并当作未来参照”的系统里,任何后期叠加都必须叠进持久化的那份,否则参照系统性地偏离现实。屏幕上的鬼影只是这个偏差的可见形态。

收尾#

改动通过了三道验证:目标板 ESP32-S3 编译(Flash 63.4%)、原版 ESP32-C3 编译(确认不破坏上游其他板子,startPortal 默认参数向后兼容)、43 个主机端单元测试全绿。版本号 bump 到 1.6.9——上一轮学到的惯例:设备遥测会带 FW-Version 头,版本号是”刷机确实落地了”的唯一可信证据。

固件已构建暂存,还没刷。刷机要等设备从深睡醒来的 USB 窗口(ESP32-S3 深睡时会从 USB 上消失,这是官方行为不是接触不良,第一篇里踩过这个坑)。刷完的验证清单:改路由器密码人为制造全灭 → 前 3 次只显示连接失败 → 第 4 次恢复屏出现、手机能扫到 TRMNL 热点 → 提交新 SSID 后恢复拉图、遥测出现 1.6.9。

至此这套系统的”失联恢复”闭环补上了:设备只要还有电,就永远给你留着一扇每 15 分钟开 5 分钟的门。

给一块深睡的墨水屏留一扇门:WiFi 恢复模式
https://blog.lishuyu.app/posts/trmnl墨水屏wifi恢复模式/
作者
猫猫魔女
发布于
2026-07-13
许可协议
CC BY-NC-SA 4.0