文章

墨水屏Homelab看板

Kindle、4.2寸墨水屏和共用的FastAPI后端

墨水屏Homelab看板

题记

家里的机房越来越热闹:绿联NAS、飞牛NAS、树莓派、华硕路由器,再加上Claude订阅的用量额度,想看一眼状态就得打开好几个网页。墨水屏不发光、不刺眼、断电也能保留画面,最适合做一块常亮的看板。于是做了两块屏:一台越狱的Kindle Paperwhite 2,和一块4.2寸的ESP8266墨水屏,背后共用同一个FastAPI后端,代码都开源在Github上:

  • kindle-dashboard:越狱Kindle的看板客户端 + 3D打印底座;
  • eink-dashboard:4.2寸墨水屏的ESP8266固件 + 3D打印外壳;
  • dashboard-backend:两块屏共用的服务端,以git submodule的形式挂在两个客户端仓库的server/目录下,改一次两边都生效。

4.2寸墨水屏实拍

演示视频

整体架构

1
2
3
4
5
6
7
8
9
10
11
12
13
[Glances]  [Home Assistant]  [Claude Usage]  [Open-Meteo]   数据源
    |             |                |              |
    +-------------+-------+--------+--------------+
                          |
                          v
                 [dashboard-backend]                  NAS上的Docker,定时采集并缓存
                          |
          +---------------+---------------+
          |                               |
  GET /dashboard.png              GET /api/eink.json
          |                               |
          v                               v
      [Kindle]                        [ESP8266]

两块屏的思路完全不同:

  • Kindle性能够、屏幕大,但越狱后能跑的东西有限,所以服务端把整张图画好,Kindle只负责下载图片并刷到屏幕上;
  • ESP8266只有几十KB内存、靠电池供电,下载和解码一张图太费电,所以服务端只下发精简JSON,由设备自己用位图字库画出来。

后端:dashboard-backend

数据采集

  • 服务器状态:每台机器装一个Glances,通过REST API取CPU、内存、温度和运行时间,存活探测支持ping/tcp/http/exec四种方式;
  • 路由器:路由器装不了Glances,就走Home Assistant中转,HACS里装AsusRouter集成,拿到CPU、内存和温度实体;
  • 环境读数:温湿度、机柜风扇等来自Home Assistant,用长期访问令牌读取;
  • Claude订阅用量:显示5小时窗口、本周全部、按模型的周限额和重置时间,服务端自持OAuth凭据,浏览器打开/claude-auth授权一次,之后自动续期;
  • 天气:默认用免Key的Open-Meteo,按服务端公网IP自动定位,也可以手动指定城市或改用心知天气,30分钟拉一次。

设计要点

  • 预采集 + 缓存:后台线程每refresh_seconds采集一次,HTTP接口只返回已经采好的结果,客户端请求永远秒回,不用等上游接口;
  • 不用无头浏览器:Kindle的看板直接用Pillow画成8位灰度PNG,镜像小、纯黑白高对比,对墨水屏很友好;
  • 配置热生效config.yaml每个采集周期都会重读,改完不用重启容器;
  • 密钥不入库HA_TOKEN放在.env里,通过环境变量注入。

墨水屏的JSON接口

给ESP8266的/api/eink.json字段名都尽量短,整个响应不到2KB:

1
2
3
4
5
6
7
8
9
{
  "rev": "内容哈希,不含时间戳",
  "next_poll": 300,
  "alert": 0,
  "servers": [{"n": "绿联 NAS", "ok": 1, "cpu": 6, "mem": 63, "temp": 51, "up": "20.9天"}],
  "claude": [{"l": "5 小时窗口", "pct": 36, "r": "02:19 重置"}],
  "env": [{"i": "temp", "l": "室内温度", "v": "28.9°C"}],
  "w": {"...": "天气"}
}
  • 设备请求时带上上一次的rev,内容没变化服务端直接返回304,设备连屏幕都不用碰;
  • next_poll由服务端决定,白天5分钟,夜间30分钟,有设备掉线时可以加快轮询;
  • 设备顺便在请求参数里上报电池电压和温湿度,可以在/api/eink/device查看。

Kindle看板:kindle-dashboard

Kindle看板实拍 Kindle看板截图

硬件

  • Kindle Paperwhite 2(758×1024,固件5.12.2.2),已越狱,装好KUAL和USBNetwork;
  • 布局按1072×1448的设计稿绘制,输出时按配置缩放,PW3/PW4的宽高比一样,换设备只需要改两个数字。

工作方式

  • dash.sh是主循环,每5分钟用curl从服务端拉一次PNG,用eips -g刷到屏幕上,每5次做一次全刷清残影;
  • 启动时先停掉Kindle的系统界面,不然右上角的状态栏每分钟会叠画到看板上,顺便关掉前光省电;
  • touch.py监听触摸事件,点屏幕右下角的「退出」会弹出确认框,确认后恢复Kindle的系统界面;
  • 离线的设备整张卡片黑底反白,一眼就能看出谁挂了。

踩过的坑

  • KUAL启动后白屏:KUAL启动的进程属于framework的进程树,停掉framework会把自己也一起杀掉,start.sh必须用setsid起一个独立会话;
  • 停framework也会白屏:framework停止时会擦屏,所以必须先停系统界面,再画第一屏
  • 状态栏叠画:5.12固件上disableEnablePillow无效,只能initctl stop framework
  • ℃显示空白:文泉驿字体里U+2103是空字形,渲染时探测到空字形就自动退回°C
  • 退出后白屏2~3分钟:这是正常现象,framework冷启动就是这么慢,所以退出时先画一张”正在恢复”的提示图。

3D打印底座

顺手用build123d画了一个参数化的充电底座:15°后倾,直头micro-USB从槽底的直通孔捅进充电口,线从底座内部的暗道走出去,免支撑就能打印。充电口位置、电源键避让槽都是用卡尺实测的,换设备改参数重新生成即可。

Kindle底座

4.2寸墨水屏:eink-dashboard

硬件

硬件用的是开源项目weather-ink-screen的4.2寸板(revB-230621,Z96屏),原本是一块天气墨水屏:

  • ESP8266(4MB Flash),深睡靠GPIO16接RST唤醒;
  • 400×300的4.2寸黑白墨水屏;
  • 时钟芯片、SHT30温湿度传感器、电池电压检测;
  • 660mAh锂电池,TC4056充电。

硬件一点没改,在这块板子上重写了一套独立固件,屏幕驱动、时钟芯片、温湿度和电压检测的逻辑从原固件移植过来,原固件的整块Flash也做了备份,随时可以刷回去。

价格预算

按淘宝、立创的常见零售价估算,单台物料成本60元左右

部件估价(元)
4.2寸墨水屏(Z96,400×300)15~30
ESP8266模组(4MB)8~12
PCB(嘉立创打样,按片均摊)3~5
CH340串口、SHT30温湿度、RTC时钟芯片、TC4056充电和LDO6~13
墨水屏升压电路、FPC座、USB-C座、按键、拨动开关、阻容等小件4~7
660mAh锂电池(带保护板)6~10
3D打印外壳(PLA约35g)2~3
合计45~80,典型60左右
  • 屏幕是价格的大头:电子价签的拆机屏只要十几二十块,换全新的同尺寸屏要45~60元;其次是SHT30,国产替代约2元,原装约7元;
  • 做第一台实际要花100~150元:PCB至少打5片,立创的阻容有起订量,淘宝和立创还要各付一次运费,可能还得买钢网或锡膏;
  • 再做一台只要50~60元:PCB和小件都有剩,主要再买屏幕、ESP8266模组、电池和传感器;
  • 没有3D打印机的话,外壳找嘉立创代打大约15~30元;烙铁、热风枪这些工具没算进去,服务端跑在现有的NAS上,也不增加硬件成本。

页面

一共6个页面,醒着的时候按键翻页:看板 → Claude用量 → 服务器详情 → 天气 → 万年历 → 设备信息。

看板 Claude用量 服务器详情 天气 万年历 设备信息
  • 顶栏的时间、温湿度、WiFi和电量都是设备本地的数据,服务端挂了也照常显示;
  • 万年历的农历、二十四节气和节日全部在设备端查表,覆盖2019~2100年,和lunar_python逐日对比零差异,不依赖网络;
  • 支持中英文界面,在配网页里切换。

省电策略

电池只有660mAh,省电是这个固件的核心,每分钟的唤醒流程大致如下:

  1. 深睡到下一个整分钟,被RTC唤醒;
  2. 读时钟芯片、SHT30和电池电压,这一步不开WiFi;
  3. 如果到了轮询时间(默认5分钟,夜间30分钟),才连WiFi请求JSON,拿完马上关WiFi;
  4. 顶栏每分钟局刷一次,内容区只有rev变化时才局刷,每小时整点全刷一次清残影;
  5. 关闭屏幕电源,继续深睡。

按设计文档里的估算,每天大约耗电17mAh(HTTP)到20mAh(HTTPS),660mAh的电池能用4~5周

项目每天次数每天耗电
分钟唤醒(不联网,局刷顶栏)1440~4mAh
联网轮询(快连 + JSON)288~11mAh
HTTPS额外开销(会话复用)288~3mAh
内容区局刷 + 整点全刷≤312~0.8mAh
深睡(0.035mA)全天~0.8mAh

配网

  • 首次上电自动进入配网模式,开一个ESP8266 E-Paper热点,手机连上会自动弹出配置页(Captive Portal);
  • 填好WiFi和服务端地址,点「测试连接」看到ok再保存;
  • 支持HTTP和HTTPS,HTTPS可以用内置的Let’s Encrypt根证书,也可以填自签证书的指纹;
  • 连公司、酒店这种要网页登录的WiFi,还支持门户自动登录,常见的Aruba门户已经内置了预设。

踩过的坑

  • 必须用ssl=all编译ssl=basic的BearSSL没有ECDHE密钥交换,现代服务器一律握手失败;
  • 华硕WiFi 6/7路由器连不上:ESP8266默认802.11n模式关联后会被踢,固件连接前强制切到WIFI_PHY_MODE_11G
  • 开放网络不能带密码:ESP8266的WiFi.begin()只要密码非空就把认证方式设成WPA,开放的访客WiFi明明扫得到却连不上;
  • 按键和屏幕共用GPIO0:屏幕初始化后GPIO0是输出,读按键时要临时切回输入,读完再恢复;
  • ADC读数偏高0.3V:ESP8266在关闭射频的唤醒里ADC基准不准,所以电压只在联网那次唤醒里采样;
  • U8g2字库超内存:原版库在ESP8266上没有把字库放进Flash,链接直接超RAM,需要打补丁。

3D打印外壳

仓库的stl/目录里有4.2寸板的外壳,就是上面实拍图里的那个,打印出来把板子和电池装进去就是一个完整的桌面摆件。

后续

两块屏摆在桌上,服务器的负载、温度,Claude还剩多少额度,抬头就能看到,比打开网页方便多了。欢迎到Github上Star和提Issue:kindle-dashboardeink-dashboarddashboard-backend

本文由作者按照 CC BY 4.0 进行授权