跳到主要内容

抄一个电子纸购物清单项目,我想抄的只有两笔预算

abanana
abanana

· 阅读约 5 分钟

前几天翻到一个仓库,papermono-shopping-list,作者 id 是 seamusc,把 M5Stack 的 PaperMono 做成贴在冰箱门上的家庭购物清单:设备挂在冰箱门上,手机浏览器里加东西,两边同步。作者自己写明了这是个人项目,只在他家一台设备上跑,能工作,但不是产品。

它做了什么我不从头讲,这种事看一眼 README 比听人转述快。我想拆开看的只有两处:刷屏策略和联网策略。电子纸加电池的设备,真正稀缺的动作就两个——刷一次屏、开一次 Wi-Fi,剩下的架构说到底都在回答「这两笔预算什么时候花」。这个项目让我愿意记一笔的地方在于,它把这两笔预算都写成了显式规则,而不是散在代码里凭手感调。

硬件底子是 ESP32-S3,配一块 3.97 英寸 480×800 的电子纸触摸屏,固件用 C++/Arduino 写,大概 2400 行。这个量级我觉得挺关键:大到能覆盖触摸、键盘、同步、电源几块,又小到能整个读完,不是那种「跑是跑起来了但没人看得懂」的规模。

刷屏这块,点击、滚动、输入全走局部刷新,不闪屏,但每做 10 次局部刷新就强制来一次全屏。逻辑大意就是一个计数器:

partial_refresh_count++

if (partial_refresh_count >= 10) {
    full_refresh();
    partial_refresh_count = 0;
}

真正的实现要跟触摸驱动的状态机对起来,核心就是这个。局部刷新快、不闪,代价是残影慢慢积累;周期性的全屏刷新把残影抹掉,顺带也算护屏。10 这个数字是拍出来的还是试出来的我没去问,但它显然是个可调量,不是写死的物理常数。

有个细节我一开始没看懂:它的灰度是拿 1 位点阵图案画出来的。翻了一会儿文档才想明白——走面板自带的灰阶模式的话,局部刷新和全屏刷新对同一块灰的呈现不一样,强制全屏那一瞬间颜色会跳一下。用空间上的抖动图案替掉灰阶模式,两种刷新下的观感就一致了。这个坑我估计自己动手写的时候还得再踩一次才记得住,先记下来。

联网那块的克制程度更高:Wi-Fi 只在同步的时候开。触发有三条路——网页里设的日程(比如夜里不同步、周末更勤)、距上次同步超过 5 分钟之后的随便一点、以及编辑完立刻同步。离线也不影响用,编辑排队写进闪存,等下次同步补上。所以清单、建议、勾选、添加这些在没信号的超市里照样能用,比「实时同步」实在得多。

想复现的话,顺序大概是这样:

  1. 先把服务器起起来。在 server/ 下建虚拟环境、装项目、把归类器关掉,再让 uvicorn 在 8000 端口监听:
python -m venv .venv
source .venv/bin/activate
pip install -e .
SHOPPING_LIST_CLASSIFIER=none uvicorn ... --port 8000

具体的模块路径去看 docs/server.md,我不在这儿抄一遍。归类器设成 none,是因为默认那条路会把新条目发给 Claude 归类,想先不接外部服务就把它关掉。

  1. 手机浏览器打开服务器的 8000 端口,进 Aisles,按你平时逛超市的走路顺序把过道加一遍。这个顺序在手机上设一次就够,设备屏幕和手机屏幕都会跟着它排。

  2. 轮到固件之前,先把设备闪存备份下来。出厂校准数据在里面,烧没了补不回来,docs/firmware.md 里写了备份、进下载模式和从浏览器烧录的整套流程。

  3. 装 PlatformIO,用 USB-C 连上 PaperMono。firmware/ 是个正常的 PlatformIO 工程,编译烧录照常规走。

验证也不复杂:点一下屏幕 → 如果距上次同步已经超过 5 分钟,应该能看到它开始同步;在手机上加一条,设备等一会儿应该刷出来;把 Wi-Fi 关掉再加两条,重新联网后这两条应该补上去。勾选和键盘建议这几样,断网状态下应该照常工作。这几步自己跑一遍,比读一遍代码更能确认那两笔预算到底花在哪。

顺带说一句,这个仓库的 docs 分得挺细:硬件、固件、架构、服务器、HTTP API、设计笔记各一份。硬件那份里有引脚图和 I²C 地址,但我觉得最值钱的是最后那节「遇到的怪问题」——这种内容教程里没人写,只有真踩过的人才写得出来。

几个我已经看到的坑,提前记一下:固件没有设备端的 Wi-Fi 设置界面,凭据是编译时写进去的,换个路由器就得重烧一次;服务器没有认证,只能放局域网,或者塞在带认证的反向代理后面;作者只在 PaperMono C153 这一个型号上测过。另外固件的板级支持、电子纸和触摸驱动、键盘布局和控件风格都是从 andrecolz 的 MonoMesh 改过来的,所以整个仓库跟着用 GPLv3,想改完再发的话这条得注意,docs/design-notes.md 里列了具体复用了哪些部分——这种「抄了哪几块写清楚」的做法本身也值得学一手。

过道归类那里还有个处理我想单独说一句:服务器第一次见到某个条目名,可以选地把名字发给 Claude 归到某个过道,但如果你后来手动把它挪到别的过道,这条修正会被保留下来。手动改的结果压过自动结果,看起来不起眼,做小工具的时候最容易漏——漏了之后每次同步都会把你的修改冲掉,用两天你就不想用了。

划重点:一是电子纸加电池的设备,先想清楚刷屏和联网这两笔预算什么时候花,再动手写界面;二是灰度用点阵图案画、局部刷新配周期性的全屏刷新,这两条可以原样搬走;三是改编别人的固件时,把「哪几块是别人的」写进文档里,比事后补授权省事得多。你可以拿手上那块电子纸试一下局部刷新加周期性全屏的组合,顺手把 10 改成 5 或 20,感受一下残影到底多久开始碍眼。

abanana
abanana

把自己踩过的坑整理成一篇能复现的笔记,写给三个月前的自己看。

查看主页 →