Files
kindle-calendar/dash/docs/layered-clock-plan.zh.md
2026-03-15 14:54:51 +08:00

13 KiB
Raw Blame History

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 唤醒次数
  • 降低全屏刷新频率
  • 减少墨水屏闪烁与残影

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.pngminute-hand/59.png
  • hour-hand/000.pnghour-hand/719.png

说明:

  • 分针每分钟一个角度,共 60
  • 时针如果要做到真正随分钟连续移动,需要 12 * 60 = 720
  • 这些都是小尺寸 patch不是整屏图体积可控
  • 所有素材统一放在 Kindle 本地 assets/ 目录下

推荐目录:

/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-handminute-hand 的中心位置不是素材端点
  • 指针的旋转中心是素材内部的圆点中心
  • Kindle 端贴图时,不能把图片底边、顶边或几何中心当作对齐基准

因此,运行时必须遵守下面的规则:

  1. 每类指针素材都要记录自己的 pivot
  2. 渲染时以 pivot 对齐表盘中心,而不是以素材端点对齐
  3. 指针长度不能写死为某个绝对像素值,而是要随表盘大小同比例缩放
  4. pivot 偏移量也必须和指针长度一起同比例缩放

也就是说,如果表盘从设计稿尺寸缩到 Kindle 运行时尺寸:

  • 时针长度随表盘直径同比例缩放
  • 分针长度随表盘直径同比例缩放
  • 时针的 pivot.x / pivot.y 也按同一比例缩放
  • 分针的 pivot.x / pivot.y 也按同一比例缩放

推荐缩放公式:

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 端最终时钟区域尺寸

这意味着:

  • 表盘主体和指针素材必须来自同一套基准设计尺寸
  • 不能单独替换某一类不同倍率的指针图,否则时针、分针与刻度会失配
  • 如果未来切换到别的机型或别的表盘尺寸,只需要换 clockRegionscale,不需要重做对齐逻辑

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 约定

建议页面支持如下参数:

/?mode=full
/?mode=background
/?mode=clock-face

7. 产物约定

推荐最终产出这几类文件:

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 建议至少包含这些字段:

{
  "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 明确写了:

因此,对 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. 1015 分钟对时钟区域补一次全刷,清理残影

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

推荐新增配置项:

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

10. 实施顺序

建议按下面顺序落地,避免一次改太多导致链路难排查。

阶段 1calendar/ 分层输出

目标:

  • full/background/clock-face 三种模式跑通
  • 每 2 小时把背景图写到 calendar/dist/kindlebg.png

输出:

  • 浏览器可预览
  • kindlebg.png 可被 nginx 直接访问

阶段 2时钟静态素材准备

目标:

  • 产出 clock-face.png
  • 产出 hour-handminute-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 规则重画分针

也就是说,背景是远端低频资源,时钟是本地高频资源,二者不要混在同一个刷新链路里