572 lines
15 KiB
Markdown
572 lines
15 KiB
Markdown
# 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
|
||
```
|
||
|
||
补充两条运行期约束:
|
||
|
||
- `clock-index.sh` 取当前时间时必须沿用 `TIMEZONE`,不能直接读系统默认时区
|
||
- `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 本地时钟素材目录结构确定
|
||
|
||
### 阶段 3:Kindle 本地分钟时钟
|
||
|
||
目标:
|
||
|
||
- 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` 规则重画分针
|
||
|
||
也就是说,**背景是远端低频资源,时钟是本地高频资源,二者不要混在同一个刷新链路里**。
|