20 KiB
Kindle Dashboard 多主题与方向方案
0. 当前实现状态(2026-03-16)
以下能力已经落地,不再只是设计目标:
calendar/网站已支持theme + orientation两维预览mode=background下不会显示预览菜单,避免污染截图calendar/已产出:dist/themes.jsondist/themes/<theme-id>.jsondist/themes/<theme-id>/<orientation>/kindlebg.png
- 兼容输出仍保留:
dist/kindlebg.pngdist/clock-region.json
- Kindle 已支持:
- 拉取
themes.json和主题级 JSON - 通过
/mnt/us/dashboard/switch-theme.sh <theme-id> [orientation]立即切换 - 优先使用本地已同步的主题背景图
- 优先使用本地已同步的主题索引和主题 JSON
- 本地没有对应背景图时,再回退到远端
background.url
- 拉取
- 同步脚本
scripts/sync-layered-clock-to-kindle.sh会先批量导出全部主题背景,再把/mnt/us/dashboard/themes/同步到设备
0.1 横向主题的抓屏评审约定
fbgrab 抓到的是 Kindle 的原始 framebuffer 图,尺寸始终是纵向的 1072 x 1448。
这意味着:
portrait主题下,physical.png与raw.png相同landscape+logo_right主题下,日常评审应优先看physical.pngraw.png只用于排查原始 framebuffer 坐标physical.png等于把 raw 图按设备实际摆放方向逆时针旋转 90 度后的结果
仓库里已经补了一个辅助脚本:
sh scripts/capture-kindle-screen.sh kindle landscape-check
它会同时输出两张图:
tmp/landscape-check-raw.pngtmp/landscape-check-physical.png
其中:
raw.png用于排查 framebuffer 原始坐标physical.png用于按 Kindle 实际摆放方向评审横向主题
1. 背景
当前链路已经稳定分成两部分:
calendar/负责渲染网页和导出背景图dash/运行在 Kindle 上,负责拉取背景图并在本地重画时钟
这次方案先明确两个概念:
theme:视觉主题,例如default、paper、classicorientation:显示方向,例如portrait、landscape
其中方向不是主题名的一部分,而是每个主题都必须包含的两套样式:
portrait- Kindle 竖向摆放
- 设备外部面板上的
kindlelogo 在下方
landscape- Kindle 横向摆放
- 设备外部面板上的
kindlelogo 在右侧
也就是说,后续系统模型应当是:
- 每个主题都包含纵向和横向两套显示样式
- Kindle 端仍然保持“拉背景图片 + 绘制时钟”的职责
- 时钟的位置、尺寸和绘制参数都由
calendar/提供
2. 目标
本方案的目标是:
- 访问网站时,可以切换主题和方向进行预览
- 网站预览效果应尽量与 Kindle 最终显示效果一致
- 当前背景生成流程继续可用,不因预览能力被破坏
calendar/提供统一的主题清单 JSON,作为主题与方向配置的单一真相源- Kindle 只需要读取固定位置的主题清单和主题配置 JSON
- Kindle 切换主题或方向后,应立即拉取对应背景并刷新
- 时钟的位置、尺寸和绘制参数由
calendar的主题配置决定,Kindle 不自行决定
非目标:
- 不在 Kindle 上重新渲染整张页面
- 不让 Kindle 维护主题路径规则
- 不让 Kindle 自行推导时钟区域布局
- 不要求第一阶段就做“本地表盘素材包”体系
3. 设计原则
3.1 calendar/ 是单一真相源
主题系统的单一真相源放在 calendar/ 侧。
建议由 calendar/ 产出:
- 一个主题清单文件,例如
themes.json - 每个主题自己的配置文件,例如
default.json、paper.json - 每个主题在不同方向下的背景图资源
其中 themes.json 至少描述:
- 有哪些主题
- 每个主题叫什么名字
- 每个主题的配置 JSON 在哪里
- 默认主题是谁
- 默认方向是谁
这样做的好处是:
- 主题路径由
calendar管理,不在 Kindle 端写死 - 新增主题时,Kindle 无需改代码里的路径规则
- 网站预览和 Kindle 切换都依赖同一套主题元数据
3.2 theme 和 orientation 分开建模
不建议把“横向主题”“纵向主题”作为主题名本身。
推荐模型是:
themeorientation
例如:
theme=default+orientation=portraittheme=default+orientation=landscapetheme=paper+orientation=portrait
这样更符合实际含义:
theme代表视觉风格orientation代表设备摆放方向与对应布局
3.3 网站预览与导出解耦
主题和方向预览只是网站访问时的交互能力,不应破坏当前导出主流程。
当前已明确三条链路:
- 预览链路:用户访问
/?mode=full&theme=<id>&orientation=<orientation>,通过页面菜单切换主题和方向 - 兼容导出链路:
export:background继续生成默认背景图,保持兼容 - 批量导出链路:
export:themes一次性生成全部主题和方向背景
也就是说:
- 预览能力已经落地
- 当前
kindlebg.png导出链路继续保留 - 多主题多方向导出已经接入
3.4 Kindle 侧最简化
Kindle 侧只保留最小能力:
- 拉取背景图
- 读取主题 JSON
- 根据当前方向读取对应 variant
- 按 JSON 提供的时钟参数绘制时钟
Kindle 不负责:
- 推导主题路径
- 决定时钟区域坐标
- 维护布局规则
推荐让 Kindle 只认识一个固定入口,例如:
https://shell.biboer.cn:20001/themes.json
然后从这个 JSON 中知道:
- 当前有哪些主题
- 默认主题和默认方向是什么
- 每个主题的配置 JSON 在哪里
3.5 当前时钟实现已经足够轻量
当前 Kindle 侧时钟是本地几何绘制,不依赖外部表盘或指针素材文件。
这意味着第一阶段主题与方向切换只需要关注:
- 背景图 URL
- 时钟区域位置和尺寸
- 本地绘制时钟所需的参数
不需要先引入复杂的“主题素材包”机制。
4. 数据约定
4.1 主题清单 themes.json
建议由 calendar/ 在固定位置提供主题清单,例如:
{
"updatedAt": "2026-03-16T10:00:00+08:00",
"defaultThemeId": "default",
"defaultOrientation": "portrait",
"themes": [
{
"id": "default",
"label": "Default",
"configUrl": "https://shell.biboer.cn:20001/themes/default.json",
"orientations": ["portrait", "landscape"]
},
{
"id": "paper",
"label": "Paper",
"configUrl": "https://shell.biboer.cn:20001/themes/paper.json",
"orientations": ["portrait", "landscape"]
},
{
"id": "classic",
"label": "Classic",
"configUrl": "https://shell.biboer.cn:20001/themes/classic.json",
"orientations": ["portrait", "landscape"]
}
]
}
第一阶段建议先保留当前主题并命名为 default,然后新增:
paperclassic
4.2 主题配置 <theme-id>.json
每个主题提供一份主题级配置,内部包含两个方向的 variant,例如:
{
"id": "default",
"label": "Default",
"variants": {
"portrait": {
"devicePlacement": "logo_bottom",
"background": {
"path": "themes/default/portrait/kindlebg.png",
"url": "https://shell.biboer.cn:20001/themes/default/portrait/kindlebg.png",
"refreshIntervalMinutes": 120
},
"clock": {
"x": 347,
"y": 55,
"width": 220,
"height": 220,
"faceRadiusRatio": 0.47,
"faceStroke": 3,
"tickOuterInset": 6,
"majorTickLength": 14,
"minorTickLength": 7,
"majorTickThickness": 4,
"minorTickThickness": 2,
"hourLengthRatio": 0.48,
"minuteLengthRatio": 0.72,
"hourThickness": 9,
"minuteThickness": 5,
"centerRadius": 7
}
},
"landscape": {
"devicePlacement": "logo_right",
"background": {
"path": "themes/default/landscape/kindlebg.png",
"url": "https://shell.biboer.cn:20001/themes/default/landscape/kindlebg.png",
"refreshIntervalMinutes": 120
},
"clock": {
"x": 795,
"y": 659,
"width": 220,
"height": 220,
"faceRadiusRatio": 0.47,
"faceStroke": 3,
"tickOuterInset": 6,
"majorTickLength": 14,
"minorTickLength": 7,
"majorTickThickness": 4,
"minorTickThickness": 2,
"hourLengthRatio": 0.48,
"minuteLengthRatio": 0.72,
"hourThickness": 9,
"minuteThickness": 5,
"centerRadius": 7,
"rotationDegrees": 90
}
}
}
}
说明:
- 背景图真实路径由主题配置提供,不在 Kindle 端拼接猜测
background.path是设备本地相对路径,用于优先读取已同步的本地背景- 每个方向都有自己的背景图和时钟区域
landscape的clock已经是 Kindle 设备坐标,不再是网页坐标landscape的rotationDegrees=90表示 Kindle 本地绘制时钟时,表盘和指针再顺时针旋转 90 度- 时钟坐标和大小来自
calendar中该主题该方向下的时钟占位,不允许 Kindle 任意改 - 后续如果某个主题在不同方向下需要不同绘制参数,也由该 JSON 一起提供
4.3 远端配置边界
远端主题配置只有两类:
themes.json- 每个主题自己的 JSON,例如
themes/default.json
除此之外:
- 背景图属于资源文件,不属于主题配置
- Kindle 本地缓存属于运行时状态,不属于主题配置
4.4 Kindle 本地状态
Kindle 本地只需要保存少量状态:
/mnt/us/dashboard/local/theme.env
/mnt/us/dashboard/local/state/themes.json
/mnt/us/dashboard/local/state/current-theme.json
/mnt/us/dashboard/local/state/theme-runtime.env
/mnt/us/dashboard/local/state/background-updated-at
其中:
theme.env记录当前THEME_ID和ORIENTATIONthemes.json是最近一次同步到本地的主题清单缓存current-theme.json是当前主题配置缓存theme-runtime.env是 Kindle 实际消费的运行时快照,包含背景路径和时钟参数
例如:
export THEME_ID=default
export ORIENTATION=portrait
4.5 Kindle 本地主题资源
当前实现中,主题背景图会随部署一起同步到 Kindle:
/mnt/us/dashboard/themes/default/portrait/kindlebg.png
/mnt/us/dashboard/themes/default/landscape/kindlebg.png
/mnt/us/dashboard/themes/paper/portrait/kindlebg.png
/mnt/us/dashboard/themes/paper/landscape/kindlebg.png
/mnt/us/dashboard/themes/classic/portrait/kindlebg.png
/mnt/us/dashboard/themes/classic/landscape/kindlebg.png
Kindle 拉背景时的规则是:
- 先读
theme-runtime.env中的BACKGROUND_PATH - 如果本地对应文件存在,直接从
/mnt/us/dashboard/themes/...拷贝 - 如果本地不存在,再回退到
BACKGROUND_URL
主题元数据同步的规则是:
- 先读本地
/mnt/us/dashboard/themes.json - 再读本地
/mnt/us/dashboard/themes/<theme-id>.json - 本地 bundle 不存在时,才回退到远端 URL
5. calendar/ 侧方案
5.1 网站访问时增加主题与方向预览菜单
预览菜单只出现在网站访问场景,不出现在背景截图结果里。
建议行为:
- 用户访问
/?mode=full时,在页面顶部显示两个可选菜单 - 一个菜单切换
theme - 一个菜单切换
orientation - 在
mode=background下不显示这些菜单,避免影响截图导出
建议 URL 形态如下:
/?mode=full&theme=default&orientation=portrait/?mode=full&theme=default&orientation=landscape/?mode=full&theme=paper&orientation=portrait
5.2 预览效果与 Kindle 效果对齐
主题预览不能只是“网页版好看”,还需要尽量接近 Kindle 最终效果。
建议做到:
- 预览和导出使用同一套主题配置
- 预览和导出使用同一套方向布局配置
- Kindle 侧按主题 JSON 中当前方向的参数绘制,避免预览和实机错位
这样评审主题时,看到的页面效果和 Kindle 最终效果才是一致的。
5.3 现有导出流程保持兼容
当前默认导出链路继续保留:
calendar/dist/kindlebg.png
calendar/dist/dashboard-manifest.json
calendar/dist/clock-region.json
兼容规则建议如下:
- 如果导出脚本没有显式传入
theme,默认导出default - 如果导出脚本没有显式传入
orientation,默认导出portrait - 现有
export:background的行为保持不变 - 默认导出继续只产出
default + portrait
这样可以保证:
- 网站预览菜单先落地
- 当前生产链路不被打断
- Kindle 当前单主题纵向使用方式继续可用
5.4 多主题多方向导出已作为附加能力落地
当前 export:themes 会生成如下产物:
calendar/dist/themes.json
calendar/dist/themes/default.json
calendar/dist/themes/default/portrait/kindlebg.png
calendar/dist/themes/default/landscape/kindlebg.png
calendar/dist/themes/paper.json
calendar/dist/themes/paper/portrait/kindlebg.png
calendar/dist/themes/paper/landscape/kindlebg.png
calendar/dist/themes/classic.json
calendar/dist/themes/classic/portrait/kindlebg.png
calendar/dist/themes/classic/landscape/kindlebg.png
这里的关键点是:
- 原有
calendar/dist/kindlebg.png继续保留 - 多主题多方向目录和
themes.json是新增能力,不是替换能力 - 主题和方向的路径管理由
calendar决定,不在 Kindle 侧固化为固定模式 - 同步到 Kindle 时,会把整个
calendar/dist/themes/目录一起带上 landscape的网页先按1448x1072渲染,再顺时针旋转 90 度,写成1072x1448的 Kindle 设备图- 因此
landscape的kindlebg.png是设备向产物,不是给桌面直接平铺预览的横图
6. Kindle 侧方案
6.1 主题发现方式
Kindle 只需要知道一个固定地址:
themes.json 的 URL
推荐两种同步方式并存:
- 被动同步:Kindle 每天拉取一次
themes.json - 主动同步:用户进入主题切换流程时,先即时拉取最新
themes.json - 如果设备上已经有本地同步的
themes.json和主题 JSON,本地 bundle 优先
这样能兼顾:
- 平时低频更新
- 切换主题时拿到最新主题清单
6.2 主题与方向切换流程
主题切换时建议执行以下流程:
- 拉取最新
themes.json - 根据用户选择的主题 ID 找到对应
configUrl - 拉取该主题对应的
<theme-id>.json - 确定当前要使用的
orientation - 从
variants[orientation]里取出背景和时钟配置 - 将
THEME_ID和ORIENTATION写入本地theme.env - 将主题 JSON 缓存在本地
- 优先读取本地
themes/<theme>/<orientation>/kindlebg.png - 如果本地没有对应背景,再回退拉取
background.url - 立即执行一次全屏刷新
- 之后继续按当前分钟级时钟刷新逻辑运行
这里有两个关键要求:
- 主题或方向切换后,不等待下一个两小时周期
- 而是立即拉取一次对应的新背景
6.3 时钟绘制保持现有模型
当前 Kindle 时钟是本地几何绘制,已经满足主题切换第一阶段需求。
因此 Kindle 侧仍然保持:
- 拉背景图片
- 读取主题 JSON
- 读取当前方向的 variant
- 用本地
lua + fbink画时钟
不需要在第一阶段增加:
- 表盘图片素材同步
- 指针图片素材同步
- 复杂的本地主题资源目录
如果未来某个主题确实需要独立表盘素材,再在第二阶段扩展。
6.4 切换入口
当前已经有一个可直接 SSH 调用的入口:
/mnt/us/dashboard/switch-theme.sh <theme-id> [orientation]
例如:
ssh kindle '/mnt/us/dashboard/switch-theme.sh default landscape'
ssh kindle '/mnt/us/dashboard/switch-theme.sh paper portrait'
如果本地刚改过页面样式,推荐先同步一次:
sh scripts/sync-layered-clock-to-kindle.sh kindle
KUAL 动态主题入口仍然是后续可选项,不是当前必需项。
如果后续要补 KUAL,目标交互仍然可以保持:
- 用户点击“Theme”
- 设备根据
themes.json获取当前可用主题清单 - 用户选择具体主题
- 用户选择
portrait或landscape - 立即切换到该主题该方向
不管具体 UI 形态如何,数据契约都应保持一致:
- 主题列表来自
themes.json - 具体切换行为来自所选主题对应的
<theme-id>.json - 最终生效资源来自该主题该方向下的 variant
7. 分阶段落地与当前进度
第一阶段:网站主题与方向预览(已完成)
目标:
- 新增
theme参数 - 新增
orientation参数 - 增加顶部预览菜单
- 默认主题命名为
default - 新增
paper和classic - 每个主题都支持
portrait和landscape
结果:
- 用户可以在网站上预览三套主题的两种方向
mode=background不显示预览控件- 现有截图导出流程保持不变
第二阶段:主题元数据发布(已完成)
目标:
calendar/产出themes.json- 每个主题产出自己的
<theme-id>.json - 每个主题在两个方向下都有独立背景图 URL
结果:
- 主题列表、方向列表、时钟配置都由
calendar/统一提供 - 主题配置里已同时提供
background.path和background.url
第三阶段:Kindle 端主题与方向切换(已完成)
目标:
- Kindle 能拉取
themes.json - Kindle 能按选择主题拉取对应的
<theme-id>.json - Kindle 能切换
portrait和landscape - 切换后立即拉取新背景并全屏刷新
结果:
- Kindle 端已具备多主题多方向切换能力
- 切换后会立即刷新
- SSH 命令切换已实机验证通过
第四阶段:按需扩展主题能力(未开始)
只有在未来确实出现需求时,再考虑:
- 为某些主题提供独立表盘素材
- 增加主题级本地资源同步
- 增加更复杂的切换交互
这一步不是当前必需项。
8. 风险与约束
8.1 预览和实机效果偏差
如果预览和导出用的不是同一套主题和方向配置,最终会出现:
- 网页看起来正确
- Kindle 上时钟错位或背景不一致
因此必须保证:
- 预览
- 导出
- Kindle 时钟绘制
都依赖同一套主题元数据和方向配置。
8.2 方向切换后的缓存问题
如果主题或方向切换后没有立即拉取新背景,Kindle 可能继续显示旧缓存。
因此切换时必须同时处理:
- 更新
THEME_ID - 更新
ORIENTATION - 拉取新的
<theme-id>.json - 拉取或读取当前方向对应的新背景图
- 重置背景更新时间戳或直接覆盖缓存背景
如果设备本地没有同步主题背景,而远端对应 PNG 也还没发布完成,Kindle 可能拉到 HTML 或错误页而不是图片。
因此当前推荐做法是:
- 同步到 Kindle 时总是一起同步
/mnt/us/dashboard/themes/ - 设备优先使用本地主题背景
- 远端
background.url只作为回退
8.3 主题清单缓存过旧
如果 Kindle 长时间只读本地缓存,可能看不到新主题或新方向配置。
因此建议:
- 日常低频同步一次
themes.json - 用户进入切换流程时再主动刷新一次
9. 当前推荐结论
推荐采用下面这条路线:
- 继续保留默认
kindlebg.png导出链路,默认仍是default + portrait - 同时保留
export:themes批量导出,作为多主题切换的资源来源 - 由
calendar继续维护themes.json和主题级<theme-id>.json - 每个主题配置里同时提供
portrait和landscape两套 variant - Kindle 继续读取固定位置的
themes.json - 切换主题或方向时优先使用本地同步的主题背景,并继续使用现有本地时钟绘制
这条路线的优点是:
- 不打断现有
kindlebg.png生成流程 - 主题和方向都由
calendar统一管理 - Kindle 侧实现最简化
- 预览和实机显示可以基于同一套配置对齐
- 即使远端主题图片还没发布完整,SSH 主题切换也能工作
- 当前时钟实现不依赖表盘和指针素材,第一阶段改造成本低