文章

VibeMate:在 ESP32-S3 圆屏上做一只会看 API 用量的桌面宠物

从硬件驱动、LVGL 页面、异步网络到运行时加固,复盘 VibeMate 桌面宠物的系统设计。

VibeMate:在 ESP32-S3 圆屏上做一只会看 API 用量的桌面宠物

VibeMate 是我最近做的一台 ESP32-S3 桌面设备:它既是一只会变化心情和饥饿度的 ASCII 宠物,也是一块 Kimi Coding Plan 用量监控屏。这个组合看似轻松,真正落到 360×360 圆形屏幕上,却同时涉及显示、触摸、电源、RTC、Wi-Fi、HTTPS、JSON 和持久化。

近期的一轮更新集中加固了运行时并清理用量页面。相比再加几个动画,我更关心这台设备能否长时间放在桌面上稳定工作。

先把硬件约束写成确定配置

目标硬件是带 ST77916 圆屏、CST816 触摸、BQ27220 电量计和 PCF85063 RTC 的 ESP32-S3 开发板。编译时四个 FQBN 选项缺一不可:16 MB Flash、对应分区表、OPI PSRAM 和启动时 CDC 串口。

这些不是“建议优化项”。缺少正确 Flash 或分区配置会直接进入启动循环;没有 OPI PSRAM,LVGL 双缓冲可能分配失败,表现为黑屏或崩溃;没开 CDC,排障时连串口日志都看不到。

嵌入式项目的 README 应该记录这种硬约束。否则别人得到的不是“构建失败”,而是一台上电后没有反馈的设备。

五个页面共用一套资源生命周期

界面通过 LVGL 组织为五页 tileview:宠物选择、宠物详情、宠物主界面、API 用量和设备状态。横向滑动切页,宠物页再叠加喂食、玩耍、对话和长按换帽子等交互。

页面多起来后,风险不是绘制本身,而是对象和定时器的生命周期。页面离开后如果动画 timer 仍持有旧对象,下一次回调就可能访问已经删除的 LVGL 节点。近期加固因此把通用组件、动画 timer 管理和页面清理解耦,让页面拥有的资源能够在销毁时成对释放。

我也把显示、触摸、I2C、电池和 RTC 拆成独立模块。这样 UI 只消费“电量百分比”“当前时间”“触摸事件”这些稳定接口,不需要知道寄存器或总线重试细节。

网络请求不能绑住界面

Kimi 用量页面需要定期通过 HTTPS 拉取周限额和 5 小时窗口用量。如果在 LVGL 事件回调里同步请求,网络抖动会直接冻结动画和触摸。

项目把 Wi-Fi/NTP 与 Kimi API 分开管理,让用量抓取在后台执行,再把结果交给 UI 更新。这里还要处理几个现实状态:

  • Wi-Fi 尚未连接;
  • API key 缺失或失效;
  • HTTP 超时;
  • 返回 JSON 缺字段;
  • 切页时请求仍在执行;
  • 设备休眠或网络重连后定时器继续工作。

“显示一个百分比”最终需要的不是一条 HTTP 请求,而是一个不会让 UI 卡死的状态机。近期对用量页的清理,核心也是减少瞬时错误对整个交互的影响。

宠物状态既要自然变化,也要可恢复

宠物系统包含 18 种 ASCII 精灵、眼睛、帽子、颜色、稀有度与 shiny 变体。饥饿度和心情会随时间衰减,喂食和玩耍再改变状态。这部分数据通过 NVS 持久化,设备重启后能够继续,而不是每次回到初始值。

状态更新需要把“当前数值”和“最后更新时间”一起保存。只保存数值,关机期间的时间就丢失;每秒都写 Flash,又会制造不必要的磨损。更稳妥的方式是按事件和合理间隔落盘,启动时根据 RTC 时间补算衰减,并对异常时间做边界保护。

调试信息也是产品的一部分

这轮更新统一了模块级调试宏,并清理了一些仅开发期需要的痕迹。串口日志必须足以回答:卡在显示初始化、I2C、Wi-Fi、TLS 还是 JSON 解析?但日志又不能泄露 Wi-Fi 密码和 API key,更不能因为高频输出拖慢主循环。

对这种跨硬件和网络的设备,我倾向于让每个模块只输出少量阶段性事件,再通过编译开关控制详细 trace。出现黑屏时,“最后一个成功阶段”通常比海量循环日志更有用。

VibeMate 的有趣之处,是把一个 API 用量数字变成了有情绪、有触感的桌面对象;它的工程挑战则恰好相反——要把所有不稳定的硬件和网络细节收进清晰的边界里,才有资格表现得轻松。

项目地址:cyijun/vibemate

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