上一篇解决了”能编出 bin”。这篇说两件互相绑定的事——怎么把 300×400 单色屏排满有用信息,以及 怎么让板子自己当 web server,浏览器直接看屏 / 改配置 / 听录音。
反射屏:不是墨水屏,但接近
板子上是 ST7305 反射式 LCD,4.2 寸、300×400、双稳态:
- 无背光——靠环境光反射成像,桌面 / 户外都能看,黑暗里完全黑屏(这反而是优点,桌面摆件晚上不晃眼)
- 类墨水屏观感,但刷新比 EPD 快得多(10ms vs 700ms)
- 1-bit per pixel:要么黑要么白,没有灰阶
物理是竖屏 300×400,软件按 Waveshare 官方 landscape 公式映射成 400×300 横屏,GRAM 布局不变,15KB framebuffer 跑 MALLOC_CAP_DMA 直送 SPI DMA:
pub const WIDTH: u16 = 400;pub const HEIGHT: u16 = 300;pub const BUF_LEN: usize = (WIDTH as usize / 2) * (HEIGHT as usize / 4); // 15_000信息密集仪表盘
400×300 不大,但 1-bit 单色屏密度可以拉满。主页 Dashboard 一屏塞了 12 项信息:

布局思路:
- 顶部 日期 + 星期 + 电源源(USB / Battery)一行带过
- 中段大字钟 用
logisoso58字体,几乎占半屏,一眼就是它 - 温湿度大数字 + sparkline 60 点历史曲线(左温右湿),瞥一眼能看趋势
- 底部进度条 APP 分区使用率 / SRAM / PSRAM 三条横条
- 页脚 uptime + reset reason + IDF 版本 + WiFi RSSI + IP
按 BOOT 或 KEY 切到第二页 GitHub 页,从 GraphQL contributionsCalendar 拉真热力图:

热力图按真实 weekday 对齐(凌晨 1 点 commit 别画到错位的日子里),最近 28 天压缩成 4 列;右侧 28-day snapshot 是 commits / active days / streak / PR / unread notifications。下面一行 LATEST 事件 + 相对时间(“10m ago”)+ commit hash,再下面是未读列表。
整套 UI 用 embedded-graphics + u8g2-fonts 画,单色屏不需要抗锯齿/混合,绘制逻辑就是一堆 Rectangle::new().fill_solid() 和 Text::new(),~1300 行 ui/mod.rs 搞定两个页面 + 配网页 + splash 启屏。
板子自己当 web server
仪表盘搞完,发现一个痛点:调 UI 不可能凑过去看屏——尤其改字体大小、位置,每次烧完跑过去对一眼太低效。索性让板子自己起 HTTP server,把 framebuffer 推成图片,浏览器直接看。
实现就几十行:每次主循环 flush 完 framebuffer,把 15KB 字节 clone 到 Arc<Mutex<Vec<u8>>>;HTTP handler 读这块共享内存,编码成 1-bit BMP(带 14 字节文件头 + 12 字节 DIB 头 + 8 字节调色板)发出去。前端 HTML 内嵌一个 <img src=/screen.bmp> + setInterval 每秒 reload,肉眼看就是实时。
// GET / → 自刷新 HTML 页(内嵌 <img src=/screen.bmp>)// GET /screen.bmp → 当前 framebuffer 编码为 1-bit BMP// POST /next → 翻页触发(等同按 KEY)// GET /settings → Tailwind 配置表单页// GET /api/config → 当前 RuntimeConfig(JSON,token 已脱敏)// POST /api/config → 更新字段,落 NVS// GET /api/wifi → 已保存 WiFi 凭据 SSID 列表// POST /api/wifi → 追加凭据 / 提升到 slot 0// POST /api/reboot → esp_restart()/system.html:硬件状态面板
主页镜屏只看得到 1-bit 的 LCD 内容,但板子内部状态远比这丰富——堆 / PSRAM / Flash / 录音存储 / 传感器 / 电池 / 时钟源。再起一个 /system.html,全用 Tailwind + Iconify 写成卡片式:

8 张卡:Identity / Memory / Flash / Storage / Sensors / Power / WiFi / Clock。每秒 fetch /api/system 拿最新 snapshot。SRAM / PSRAM / App 分区都有横向进度条,颜色按使用率梯度(红/橙/绿)。
为啥要做手机端?因为板子放桌面摆件场景,懒得每次开 PC——掏手机扫 IP 直接看:

/settings:运行时改配置
刷固件麻烦,能在线改的全做成运行时配置。15 个字段都进 NVS:GitHub user / token、refresh 周期、时区偏移、传感器校准、splash 动画次数、自动翻页周期……加 WiFi 凭据 CRUD。
桌面版:

手机版:

GitHub token “Discover” 按钮是个小巧思——粘进去原始 token 点一下,板子自己请求 https://api.github.com/user 反查 username 自动填表。
/logs:实时日志流
调错最实用的页。log_sink 把 log crate 的所有输出 hook 进一个环形 buffer,HTTP 端用 SSE 推过来:

不用插 USB、不用开 espflash monitor,浏览器开着就行。手机也能看,半夜电池没电关机为啥能 grep 一下。
SoftAP + HTTP 配网:把 BLE 砍了
最初版本是 BLE 配网——esp-idf-svc 有现成的 BleProvisioner,跑通了,但发现两个问题:
- BLE 栈占内部 SRAM ~30KB,开了之后
heap_min_ever直接逼 10KB - 手机端要装专门 App(ESP BLE Provisioning),用户体验劝退
改成 SoftAP + HTTP 门户:板子开 CuriosityLab-Setup(open,无密码),手机连上浏览器打开 192.168.4.1 填表单,提交后切 STA。整个 BLE 栈砍掉,sdkconfig 一行 CONFIG_BT_ENABLED=n,SRAM 直接回血。
一句话总结
屏只有 400×300,但板子有 16MB Flash 和 8MB PSRAM——能跑 web server,能跑 HTTPS 客户端拉 GitHub 数据,能编码图像。桌面摆件不该只是显示,它本来就是一台塞在 4.2 寸壳子里的 Linux 机器(虽然跑的是 FreeRTOS)。