Files
kindle-calendar/dash/docs/layered-clock-plan.zh.md
2026-03-17 10:37:27 +08:00

572 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Kindle Dashboard 分层时钟方案
## 1. 背景
当前仓库里有两部分:
- `calendar/`:负责渲染仪表盘网页与导出背景素材
- `dash/`:运行在 Kindle 上,负责拉图、刷屏、休眠与唤醒
最新设计稿来自 Figma
- 文件:`calendar`
- 节点:`6:2`
- 链接:`https://www.figma.com/design/3bXFNM5nM6mCq0TpL3nPYK/calendar?node-id=6-2&m=dev`
指针与表盘素材的拆分约束来自 Figma annotation
- 节点:`23:418`
- 链接:`https://www.figma.com/design/3bXFNM5nM6mCq0TpL3nPYK/calendar?node-id=23-418&m=dev`
该节点 annotation 已明确:
- `阳历当天`
- `星期`
- `农历日`
- `时钟区域`
- `日历`
- `天气预报卡片`
- `书摘卡片`
本方案基于这些约束,采用“低频背景 + 本地时钟”的拆分方式:
- `calendar/``2 小时` 生成一次背景图
- 背景图直接写到:
- `/Users/gavin/kindle-dash/calendar/dist/kindlebg.png`
- Kindle 通过固定 HTTPS 地址拉取背景图:
- `https://shell.biboer.cn:20001/kindlebg.png`
- Kindle 端每分钟`不联网`
- Kindle 端只在本地重画时钟区域
## 2. 目标
这次改造不是单纯调样式,而是把页面拆成两类刷新节奏完全不同的素材:
1. 低频背景层
2. 高频时钟层
目标收益:
- 背景内容不再分钟级刷新
- 分钟级变化只限制在时钟区域
- 降低 Wi-Fi 唤醒次数
- 降低全屏刷新频率
- 减少墨水屏闪烁与残影
### 2.1 当前导出基线
基于 Kindle Voyage 系统自带屏保 `bg_default.png` 的实机验证,当前背景导出链路应遵守这几个固定约束:
- 网页成品页尺寸直接使用 `1072 x 1448`
- Kindle 主背景图直接导出为同尺寸,不再做额外旋转、补边或缩放
- 导出格式固定为 `8-bit grayscale PNG`
- 页面视觉尽量保持纯白底、纯黑文字与图标,避免大面积灰阶装饰
这部分不是临时调试参数,而是后续继续微调版式时的默认基准。
当前设备端时钟实现也已经切换为:
- Kindle 本机 `lua` 生成时钟区位图
- 通过 `fbink` 刷新指定矩形
在 Kindle Voyage 5.13.6 的当前环境里,还需要额外注意一点:
- `fbink` 叠加图片时必须带 `-V` / `--noviewport`
- 否则会因为 viewport 修正导致时钟区域纵向偏移
- 并且会在同一帧缓冲里出现两个重叠时钟
也就是说Voyage 上本地时钟叠加的稳定命令形态应当是:
```sh
fbink -q -V -g "file=/mnt/us/dashboard/local/state/clock-render.pgm,x=$CLOCK_REGION_X,y=$CLOCK_REGION_Y"
```
- 不再依赖 `eips + 透明 PNG patch` 叠图
## 3. 分层边界
### 3.1 全屏背景层
背景层文件名固定为:
- `kindlebg.png`
背景层包含:
- 整体外层容器、圆角、阴影、背景渐变
- 日历卡片中的:
- 阳历当天数字
- 星期
- 农历日
- 下方月历区域
- 天气卡片全部内容
- 书摘卡片全部内容
背景层**不包含**
- 时钟表盘主体
- 时钟刻度
- 时针
- 分针
- 中心圆点
也就是说,背景图在时钟区域只保留布局占位,不直接承载任何分钟级内容。
### 3.2 静态表盘 patch
静态表盘 patch 为一个独立本地素材:
- `clock-face.png`
该素材包含:
- 圆形表盘主体
- 刻度
- 中心底座或中心圆盘
建议尺寸直接对齐 Figma 时钟区域:
- 节点:`24:74`
- 设计尺寸:`220 x 220`
这张图应当保存在 Kindle 本地,例如:
- `/mnt/us/dashboard/assets/clock-face.png`
它不需要分钟级联网拉取,只需要在部署时同步到设备,或者在更换设计稿时重新同步。
### 3.3 指针层
高频变化层只包含:
- 时针
- 分针
这里不建议包含秒针:
- 墨水屏收益低
- 刷新频率会显著上升
- 更容易产生残影
由于当前 `dash/` 链路本质是 `eips` 刷图,不是 SVG/Canvas 实时渲染,所以推荐使用`分层素材方案`
- `minute-hand/00.png``minute-hand/59.png`
- `hour-hand/000.png``hour-hand/719.png`
说明:
- 分针每分钟一个角度,共 `60`
- 时针如果要做到真正随分钟连续移动,需要 `12 * 60 = 720`
- 这些都是小尺寸 patch不是整屏图体积可控
- 所有素材统一放在 Kindle 本地 `assets/` 目录下
推荐目录:
```text
/mnt/us/dashboard/assets/clock-face.png
/mnt/us/dashboard/assets/hour-hand/000.png ... 719.png
/mnt/us/dashboard/assets/minute-hand/00.png ... 59.png
```
如果后续确认 Kindle 端存在稳定的本地绘线工具,再考虑把指针改成算法绘制;当前版本不依赖这个前提。
### 3.4 指针锚点与缩放规则
这部分是 Kindle 端渲染能否正确对齐的关键约束,不能省略。
根据 Figma 节点 `23:418` 的 annotation 与素材结构:
- `hour-hand``minute-hand` 的中心位置不是素材端点
- 指针的旋转中心是素材内部的圆点中心
- Kindle 端贴图时,不能把图片底边、顶边或几何中心当作对齐基准
因此,运行时必须遵守下面的规则:
1. 每类指针素材都要记录自己的 `pivot`
2. 渲染时以 `pivot` 对齐表盘中心,而不是以素材端点对齐
3. 指针长度不能写死为某个绝对像素值,而是要随表盘大小同比例缩放
4. `pivot` 偏移量也必须和指针长度一起同比例缩放
也就是说,如果表盘从设计稿尺寸缩到 Kindle 运行时尺寸:
- 时针长度随表盘直径同比例缩放
- 分针长度随表盘直径同比例缩放
- 时针的 `pivot.x / pivot.y` 也按同一比例缩放
- 分针的 `pivot.x / pivot.y` 也按同一比例缩放
推荐缩放公式:
```text
scale = min(runtime_clock_width / design_clock_width, runtime_clock_height / design_clock_height)
rendered_hand_width = source_hand_width * scale
rendered_hand_height = source_hand_height * scale
rendered_pivot_x = source_pivot_x * scale
rendered_pivot_y = source_pivot_y * scale
```
其中:
- `design_clock_width / design_clock_height` 指 Figma 导出该套素材时所对应的基准表盘尺寸
- `runtime_clock_width / runtime_clock_height` 指 Kindle 端最终时钟区域尺寸
这意味着:
- 表盘主体和指针素材必须来自同一套基准设计尺寸
- 不能单独替换某一类不同倍率的指针图,否则时针、分针与刻度会失配
- 如果未来切换到别的机型或别的表盘尺寸,只需要换 `clockRegion``scale`,不需要重做对齐逻辑
## 4. 为什么不能只把表盘放进整页背景
这个问题必须单独说明。
如果:
- 表盘主体和刻度只存在于 `kindlebg.png`
- Kindle 每分钟只覆盖新的时针/分针
那么上一分钟留下的旧指针就无法被干净擦除。
因此 Kindle 端每分钟的正确流程应该是:
1. 先重画一张本地 `clock-face.png`
2. 再叠加新的时针素材
3. 再叠加新的分针素材
这等价于“先擦除,再重画”,并且整个流程不依赖网络。
所以本方案不是“背景图 + 两根指针”两层,而是:
1. `kindlebg.png`:全屏低频背景
2. `clock-face.png`:本地静态表盘 patch
3. `hour-hand/*.png + minute-hand/*.png`:本地高频指针素材
## 5. 数据刷新策略
### 5.1 背景层刷新
背景层刷新触发条件:
-`2 小时` 一次
- 跨天时立即刷新一次
- 天气接口异常恢复后可补刷一次
推荐调度:
- `00:00 / 02:00 / 04:00 / ... / 22:00`
背景层刷新输出:
- `/Users/gavin/kindle-dash/calendar/dist/kindlebg.png`
背景层对 Kindle 的访问地址固定为:
- `https://shell.biboer.cn:20001/kindlebg.png`
### 5.2 静态表盘 patch 刷新
静态表盘 patch 不参加分钟级调度。
建议刷新方式:
- 随部署同步一次
- Figma 设计或尺寸变化时重新导出并同步
### 5.3 指针层刷新
指针层刷新触发条件:
- 每分钟一次
指针层数据只依赖 Kindle 本地时间:
- 小时
- 分钟
分钟刷新不需要联网。
## 6. 网页渲染模式设计
为了让 `calendar/` 稳定产出背景与表盘素材,建议支持下面 3 种模式。
### 6.1 `full`
用途:
- 本地开发预览
- 对照 Figma 联调
输出内容:
- 背景层
- 表盘
- 指针预览
### 6.2 `background`
用途:
- 生成 `kindlebg.png`
输出内容:
- 只渲染背景层
- 时钟区域保留占位,但不绘制表盘与指针
### 6.3 `clock-face`
用途:
- 生成静态表盘 patch
输出内容:
- 只渲染表盘主体、刻度和中心底座
- 不绘制时针、分针
### 6.4 URL 约定
建议页面支持如下参数:
```text
/?mode=full
/?mode=background
/?mode=clock-face
```
## 7. 产物约定
推荐最终产出这几类文件:
```text
calendar/dist/kindlebg.png
calendar/dist/dashboard-manifest.json
kindle local:
/mnt/us/dashboard/assets/clock-face.png
/mnt/us/dashboard/assets/hour-hand/000.png ... 719.png
/mnt/us/dashboard/assets/minute-hand/00.png ... 59.png
```
`manifest` 建议至少包含这些字段:
```json
{
"background": {
"path": "kindlebg.png",
"url": "https://shell.biboer.cn:20001/kindlebg.png",
"updatedAt": "2026-03-15T10:00:00+08:00",
"refreshIntervalMinutes": 120
},
"clockRegion": {
"x": 313,
"y": 0,
"width": 220,
"height": 220
},
"clockFace": {
"path": "assets/clock-face.png",
"managedOnKindle": true,
"designWidth": 220,
"designHeight": 220
},
"clockHands": {
"hourPattern": "assets/hour-hand/%03d.png",
"minutePattern": "assets/minute-hand/%02d.png",
"refreshIntervalMinutes": 1,
"networkRequired": false,
"anchorMode": "pivot",
"scaleWithClockFace": true,
"hourHand": {
"sourceDesignWidth": 220,
"sourceDesignHeight": 220,
"pivotSource": "figma-annotation-center-dot"
},
"minuteHand": {
"sourceDesignWidth": 220,
"sourceDesignHeight": 220,
"pivotSource": "figma-annotation-center-dot"
}
}
}
```
说明:
- `x/y/width/height` 先按设计稿记录
- 真正接入 Kindle 时,要换算成最终截图分辨率下的实际像素值
- `pivotSource` 表示指针锚点来自 Figma 素材内部圆点中心,而不是素材端点
- `scaleWithClockFace=true` 表示运行时必须按表盘尺寸同步缩放指针长度与 `pivot`
## 8. Kindle 侧刷新策略
### 8.1 已确认的前提
`eips` 支持把 PNG/JPG 绘制到指定坐标,参数包含:
- `-g`
- `-x`
- `-y`
- `-f`
MobileRead Wiki 明确写了:
- `eips -g|-b image_path [-w waveform -f -x xpos -y ypos -v]`
- `-x``-y` 以像素为单位
来源:<https://wiki.mobileread.com/wiki/Eips>
因此,对 Kindle Voyage 而言,“在固定时钟区域重画小图”这个前提是成立的。
### 8.2 推荐流程
#### 启动或背景刷新时
1. 通过 HTTPS 拉取:
- `https://shell.biboer.cn:20001/kindlebg.png`
2. 保存为本地背景缓存
3. 使用全屏刷新显示背景图
4. 在时钟区域重画一次:
- `clock-face.png`
- 当前时针
- 当前分针
#### 每分钟刷新时
1. 读取 Kindle 本机时间
2. 计算:
- `minute_index = 00..59`
- `hour_index = ((hour % 12) * 60 + minute) = 000..719`
3. 在固定坐标先画:
- `clock-face.png`
4. 根据当前 `clockRegion` 与素材基准尺寸计算统一缩放比例
5. 以素材内部 `pivot` 对齐表盘中心,先画:
- `hour-hand/<hour_index>.png`
6. 再以素材内部 `pivot` 对齐表盘中心,画:
- `minute-hand/<minute_index>.png`
7. 默认做局部/普通刷新
8.`10``15` 分钟对时钟区域补一次全刷,清理残影
### 8.3 功耗模型
这套方式的功耗来源拆成两类:
- 背景刷新:
- 每 2 小时联网一次
- 全屏刷新一次
- 时钟刷新:
- 每分钟本地刷一次小区域
- 不联网
相比“每分钟拉一张整屏背景图”,这会明显省电。
## 9. 代码改造建议
### 9.1 `calendar/`
建议改造点:
- 新增 `mode=background`
- 新增 `mode=clock-face`
- 时钟区域从日历/天气/书摘中完全拆开
- 增加一个定时生成任务,每 2 小时把背景图写到:
- `/Users/gavin/kindle-dash/calendar/dist/kindlebg.png`
- 生成 `dashboard-manifest.json`
### 9.2 `dash/`
建议改造点:
- `dash/src/local/fetch-dashboard.sh`
- 改成只拉取 `kindlebg.png`
- `dash/src/dash.sh`
- 从“单一刷新循环”改为“背景刷新 + 本地时钟刷新”双节奏
- 新增例如:
- `dash/src/local/render-clock.sh`
- `dash/src/local/render-clock-face.sh`
- `dash/src/local/clock-index.sh`
推荐新增配置项:
```sh
export BACKGROUND_URL="https://shell.biboer.cn:20001/kindlebg.png"
export BACKGROUND_REFRESH_SCHEDULE="0 */2 * * *"
export CLOCK_REGION_X=313
export CLOCK_REGION_Y=0
export CLOCK_REGION_WIDTH=220
export CLOCK_REGION_HEIGHT=220
export CLOCK_FULL_REFRESH_INTERVAL_MINUTES=15
export PRE_SLEEP_GRACE_SECONDS=10
```
补充两条运行期约束:
- `TIMEZONE` 只用于 `next-wakeup` 的 cron 时区计算;`clock-index.sh` 需要单独走固定 UTC 偏移或设备可识别的时区配置,不能直接退回系统默认时区
- `PRE_SLEEP_GRACE_SECONDS` 这类“进入休眠前的可中断窗口”必须从实际休眠时长里扣掉,否则分钟刷新会长期落后一个节拍
当前实现里,时钟区域的适配已经分成两层:
- `CLOCK_REGION_X/Y/WIDTH/HEIGHT` 负责位置与尺寸
- `CLOCK_*` 外观参数负责指针长度、粗细、刻度长度和圆心点大小
因此,页面板式变化导致时钟区域变大变小时,一般不需要重画任何静态素材,也不需要改 Lua 代码;
只要重新导出网页拿到新的 `clock-region.json`,同步到 Kindle 后,本机时钟就会按新尺寸重画。
## 10. 实施顺序
建议按下面顺序落地,避免一次改太多导致链路难排查。
### 阶段 1`calendar/` 分层输出
目标:
- `full/background/clock-face` 三种模式跑通
- 每 2 小时把背景图写到 `calendar/dist/kindlebg.png`
输出:
- 浏览器可预览
- `kindlebg.png` 可被 nginx 直接访问
### 阶段 2时钟静态素材准备
目标:
- 产出 `clock-face.png`
- 产出 `hour-hand``minute-hand` 素材库
- 明确每类指针素材的 `pivot` 与基准表盘尺寸
- 确保时针、分针长度能随表盘尺寸同比例缩放适配
输出:
- Kindle 本地时钟素材目录结构确定
### 阶段 3Kindle 本地分钟时钟
目标:
- Kindle 每分钟本地重画时钟
- 不联网
输出:
- 背景低频更新 + 时钟本地高频更新的完整链路
## 11. 推荐结论
当前最稳妥的推进顺序是:
1. 先让 `calendar/` 每 2 小时稳定生成:
- `/Users/gavin/kindle-dash/calendar/dist/kindlebg.png`
2. 再让 Kindle 只从:
- `https://shell.biboer.cn:20001/kindlebg.png`
拉背景图
3. 时钟区域完全本地化:
- 本地 `clock-face.png`
- 本地 `hour-hand/*.png`
- 本地 `minute-hand/*.png`
4. 分钟刷新时:
- 先重画表盘
- 再按 `pivot + scale` 规则重画时针
- 再按 `pivot + scale` 规则重画分针
也就是说,**背景是远端低频资源,时钟是本地高频资源,二者不要混在同一个刷新链路里**。