update at 2026-03-17 10:37:27

This commit is contained in:
douboer@gmail.com
2026-03-17 10:37:27 +08:00
parent e5becf63cf
commit 192eb1b8d1
44 changed files with 5208 additions and 403 deletions

View File

@@ -0,0 +1,115 @@
# Kindle Voyage 5.13.6 新机 Bootstrap 说明
## 目标
把“同型号新机拉齐能力”收敛成一个单入口脚本:
- 预置 `WatchThis` payload
- 预置 `KUAL / MRPI / USBNetwork / kindle-dash`
- 预置 SSH 恢复脚本
- SSH 打通后自动同步 dashboard、切主题、立即出图
对应脚本:
- [bootstrap-new-kindle.sh](/Users/gavin/kindle-dash/bootstrap-new-kindle.sh)
## 先说结论
这个脚本不是“100% 零交互刷机”。
当前仍然不能完全自动化的部分有:
1. `WatchThis` demo 流程本身需要设备端手势与点击
2. `;log mrpi` 需要在 Kindle 搜索栏手工触发
3. 首次 SSH 最稳的方式仍然是在 `KTerm` 里执行:
```sh
sh /mnt/us/ssh-force-dropbear-22.sh
```
所以这套 bootstrap 的真实定位是:
- 尽量把 Mac 侧和文件预置自动化
- 把设备侧必须手工的动作压到最少
## 脚本模式
### 1. `prepare-storage`
当 Kindle 以 USB 存储方式挂载到 Mac 后执行:
```sh
sh bootstrap-new-kindle.sh prepare-storage
```
它会自动:
-`KV-5.13.6.zip``demo.json` 放到 `.demo/`
- 创建 `.demo/goodreads/`
-`Update_hotfix_watchthis_custom.bin` 放到 Kindle 根目录
-`extensions/``mrpackages/``dashboard/` 预置到 Kindle
-`scripts/kindle/*.sh` 拷到 Kindle 根目录,供 `KTerm` 使用
### 2. `post-ssh`
当 Kindle 已连上 WiFi且你已经在 `KTerm` 拉起 `dropbear` 后执行:
```sh
sh bootstrap-new-kindle.sh post-ssh
```
它会自动:
- 修复设备侧 SSH 辅助脚本权限
- 尝试同步 `authorized_keys`
- 同步 dashboard 运行时和主题包
- 立即切到指定主题并把背景画到屏幕上
可选:
```sh
sh bootstrap-new-kindle.sh post-ssh -t simple -o portrait
sh bootstrap-new-kindle.sh post-ssh --start-dashboard
```
### 3. `all`
默认模式:
```sh
sh bootstrap-new-kindle.sh
```
逻辑是:
- 如果检测到 `/Volumes/Kindle`,先做 `prepare-storage`
- 如果同时检测到 `ssh kindle` 可用,再继续做 `post-ssh`
- 哪一段当前做不了,就明确打印下一步人工动作
## 设备侧最短人工步骤
1. 恢复出厂并进入 demo mode
2. 到真正的 `Sideload Content` 时机
3. 让脚本已预置好的 `.demo` payload 生效
4. 通过 `Get Started` 完成越狱
5. 搜索 `;log mrpi`
6.`KUAL` 中先执行 `Rename OTA Binaries -> Rename`
7. 连上 WiFi
8. 打开 `KTerm`,执行:
```sh
sh /mnt/us/ssh-force-dropbear-22.sh
```
9. 回到 Mac执行
```sh
sh bootstrap-new-kindle.sh post-ssh
```
## 相关文档
- WatchThis 越狱路径:
[kindle-voyage-5.13.6-watchthis-zh.md](/Users/gavin/kindle-dash/dash/docs/kindle-voyage-5.13.6-watchthis-zh.md)
- SSH 打通与 KTerm 兜底:
[kindle-voyage-5.13.6-dual-ssh-playbook-zh.md](/Users/gavin/kindle-dash/dash/docs/kindle-voyage-5.13.6-dual-ssh-playbook-zh.md)

View File

@@ -12,6 +12,12 @@
- 白屏出现时dashboard 本身往往没有真正接管成功,更像是 `framework/KUAL` 启动链在中途被打断
- 当前最稳定的恢复路径,仍然是通过 SSH 执行 `./stop.sh`
补充记录一个当前仍未修住、但边界已经比较清楚的问题:
-`KUAL` 进入 dashboard 后,再尝试回到 dashboard / KUAL 的原生 UI 路径,仍可能落入白屏
- 这个问题不应再继续归因到背景图、时钟或页面布局
- 当前更合理的判断,仍然是 `KUAL -> start.sh -> dash.sh` 的切换链路不稳定
这份文档只记录当前交接结论,不再继续尝试修复。
## 已确认的事实
@@ -290,6 +296,23 @@ ssh kindle 'start webreader'
而不是继续怀疑背景图、时钟绘制或顶栏遮罩。
进一步说,当前建议把这个遗留问题固定表述为:
-`KUAL -> Kindle Dashboard``dashboard -> 原生 UI/KUAL` 之间的边界切换不稳定,表现为白屏”
建议的修复方向是:
1. 不要让 KUAL 直接同步执行 `/mnt/us/dashboard/start.sh`
2. 改成由一个独立 wrapper 先脱离 KUAL / framework 会话,再延迟启动 dashboard
3. 继续保留 `ssh kindle 'cd /mnt/us/dashboard && DEBUG=true ./start.sh'` 作为唯一已验证稳定的进入方式
4. 继续保留 `./stop.sh` 作为唯一已验证稳定的退出恢复方式
等 SSH 恢复后,再围绕下面三点做实机验证:
1. KUAL wrapper 是否还能触发 `framework` 被 TERM
2. `start.sh` 的后台脱离方式是否足够彻底
3. `stop.sh` 后是否还需要补 `start webreader`
## 这轮涉及的关键文件
- [dash/src/dash.sh](/Users/gavin/kindle-dash/dash/src/dash.sh)

View File

@@ -499,7 +499,7 @@ export PRE_SLEEP_GRACE_SECONDS=10
补充两条运行期约束:
- `clock-index.sh` 取当前时间时必须沿用 `TIMEZONE`,不能直接系统默认时区
- `TIMEZONE` 只用于 `next-wakeup` 的 cron 时区计算;`clock-index.sh` 需要单独走固定 UTC 偏移或设备可识别的时区配置,不能直接退回系统默认时区
- `PRE_SLEEP_GRACE_SECONDS` 这类“进入休眠前的可中断窗口”必须从实际休眠时长里扣掉,否则分钟刷新会长期落后一个节拍
当前实现里,时钟区域的适配已经分成两层:

View File

@@ -0,0 +1,415 @@
# Kindle Dashboard 双翻页键主题菜单方案
## 0. 当前状态
本文是评审方案,不是实机结论。
当前 Kindle 已掉出 Wi-FiSSH 中断,因此这份文档的目标是:
- 先把“左右同时按下翻页键,呼出主题选择页面”的方案固定下来
- 明确交互、进程模型、文件落点和风险
- 等 SSH 恢复后,再按这份方案做实机联调和裁剪
本文默认设备为:
- Kindle Voyage
- 固件 5.13.6
- dashboard 运行时会停掉 framework / webreader
## 1. 已确认事实
### 1.1 左右翻页键在内核里是两个独立键
实机上已经确认:
- 输入设备名为 `fsr_keypad`
- 设备节点为 `/dev/input/event2`
- `evtest /dev/input/event2` 可识别:
- `Event code 104 (PageUp)`
- `Event code 109 (PageDown)`
这意味着:
- 左右翻页键不是一个“翻页动作”
- 而是两个可以分别监听的 `EV_KEY`
- 软件层可以把它们组合成一个“组合键”
### 1.2 组合键不可能在同一时刻上报
Linux 输入事件一定是串行上报的。
所以“左右同时按下”在实现上必须解释为:
- 两个键在一个很短的时间窗口内先后进入 pressed
而不能解释为:
- 两个键共享同一个内核时间戳
推荐窗口:
- `300ms``350ms`
### 1.3 真正系统挂起时,用户态监听器不会继续工作
当前 dashboard 会在休眠路径里进入:
```sh
echo "mem" >/sys/power/state
```
一旦进入这个状态:
- 用户态 shell / Lua / evtest 监听器都会停掉
- 无法继续等待双键输入
因此本方案的有效范围是:
- dashboard 正在运行
- Kindle 尚未进入真正的 `mem` 挂起
这也是为什么本方案先聚焦:
- 运行态主题菜单
而不把目标直接扩展为:
- 真休眠态唤醒菜单
## 2. 目标
目标能力如下:
1. 用户在 dashboard 运行态下,同时按下左右翻页键
2. 屏幕弹出一个 KUAL 风格的主题选择页面
3. 用户通过翻页键在主题列表中移动选中项
4. 用户再次同时按下左右翻页键,确认并切换主题
5. 切换完成后,立即刷新背景和时钟
本阶段非目标:
- 不做真正休眠态下的按键唤醒菜单
- 不在 Kindle 上实现复杂触摸手势
- 不做缩略图式主题画廊
- 不做 theme + orientation 同时编辑的完整设置页
## 3. 推荐交互
### 3.1 触发方式
触发手势:
- 左右翻页键在 `350ms` 内同时按下
解释规则:
- 先按左,再按右,且间隔不超过 `350ms`,视为组合键
- 先按右,再按左,且间隔不超过 `350ms`,也视为组合键
- 单独按左或单独按右,不触发菜单
### 3.2 菜单打开后的操作
推荐第一版交互保持纯物理键可用:
- `PageUp`:向上移动
- `PageDown`:向下移动
- 再次双键同时按下:确认当前主题
这样做的原因:
- 不依赖 framework
- 不依赖系统触摸 UI
- 行为闭环简单,便于在无 SSH 时本机恢复
### 3.3 方向处理
推荐第一版只切换 `theme id`,保持当前方向不变。
例如:
- 当前是 `simple / landscape`
- 菜单里选中 `paper`
- 最终切到 `paper / landscape`
如果目标主题不支持当前方向,再回退到:
- 该主题可用的默认方向
这样能避免菜单第一版就把交互复杂度拉高到二维。
## 4. 菜单布局
### 4.1 布局风格
菜单布局可以模拟 KUAL但不追求逐像素复刻。
推荐视觉结构:
- 全屏白底
- 顶部两行标题
- `Kindle Dashboard`
- `Theme Menu`
- 中部竖向列表
- 当前选中项前用 `>` 标识
- 底部两行提示文案
示意:
```text
Kindle Dashboard
Theme Menu
Orientation: landscape
Selected: Simple
--------------------------------
> Default (default)
Paper (paper)
Classic (classic)
Simple (simple)
PageUp/PageDown: move
Press both keys: apply
```
### 4.2 为什么不做缩略图页
在当前阶段,不建议直接做缩略图式主题预览页。
原因:
- 需要更多图形绘制和排版能力
- 需要解决选中态、滚动和点击区域映射
- 在 SSH 不稳定时,调试成本显著上升
所以推荐第一版先做:
- 纯文本 KUAL 风格列表
确认流程稳定后,再考虑升级到:
- 缩略图 + 文本说明
## 5. 进程模型
### 5.1 推荐采用独立监听服务
推荐新增一个独立后台服务,例如:
- `dash/src/local/theme-menu-service.sh`
职责:
- 独立监听 `/dev/input/event2`
- 识别组合键
- 维护菜单状态
- 在确认后调用现有主题切换链路
不建议把这套逻辑直接塞进 `dash.sh` 主循环。
原因:
- 主循环核心职责已经是拉图、刷屏、休眠
- 组合键菜单属于输入状态机
- 两者耦合后,调试和排错都会变差
### 5.2 与现有链路的关系
推荐关系如下:
1. `dash.sh` 启动时拉起 `theme-menu-service.sh`
2. `theme-menu-service.sh` 常驻监听 `event2`
3. 识别到组合键后,本机绘制菜单
4. 用户确认后,调用现有:
- `/mnt/us/dashboard/switch-theme.sh <theme-id> [orientation]`
5. `switch-theme.sh` 继续负责:
- 同步主题配置
- 拉最新背景
- 刷新屏幕
- 重绘时钟
也就是说,菜单服务只负责:
- 触发
- 选择
- 调用已有切换入口
而不是重新实现一套主题切换。
## 6. 事件状态机
### 6.1 空闲态
空闲态只关注组合键触发。
状态变量:
- `pending_key`
- `pending_time`
- `combo_window_seconds`
逻辑:
1. 收到 `PageUp down`
- 记录 `pending_key=up`
2. 收到 `PageDown down`
- 如果此前 `pending_key=up` 且时间差小于窗口,判定为组合键
3. 反向顺序同理
4. 组合键成立后,打开菜单
### 6.2 菜单态
菜单态下,状态机切成另一套规则:
- `PageUp` release选中项上移
- `PageDown` release选中项下移
- 组合键:确认当前项
推荐选中项循环:
- 第一项再上移 -> 跳到最后一项
- 最后一项再下移 -> 跳到第一项
这样可以减少边界判断和失败反馈。
## 7. 文件落点
推荐新增或修改这些文件。
### 7.1 新增
- `dash/src/local/theme-menu-service.sh`
- 常驻监听器和菜单状态机
### 7.2 修改
- `dash/src/local/theme-json.lua`
- 新增 `list` 子命令
- 输出主题列表给菜单服务消费
- `dash/src/local/env.sh`
- 新增菜单相关开关,例如:
- `THEME_MENU_ENABLED`
- `THEME_MENU_EVENT_DEVICE`
- `THEME_MENU_COMBO_WINDOW_SECONDS`
- `dash/src/dash.sh`
- 在初始化阶段拉起菜单监听服务
- `dash/src/stop.sh`
- 停止 dashboard 时顺手停止菜单服务
- `scripts/sync-layered-clock-to-kindle.sh`
- 把新增服务脚本同步到设备
## 8. 推荐配置
建议新增这些环境变量:
```sh
export THEME_MENU_ENABLED=true
export THEME_MENU_EVENT_DEVICE="/dev/input/event2"
export THEME_MENU_COMBO_WINDOW_SECONDS=0.35
```
说明:
- `THEME_MENU_ENABLED`
- 总开关
- `THEME_MENU_EVENT_DEVICE`
- Voyage 上通常是 `event2`
- 后续换机型时可以只改这个
- `THEME_MENU_COMBO_WINDOW_SECONDS`
- 组合键判定窗口
## 9. 风险与限制
### 9.1 真休眠态不可用
这是本方案最重要的限制。
只要系统真的进入:
- `echo "mem" >/sys/power/state`
那么:
- 监听器就不会继续运行
- 双键菜单也不会再响应
因此第一版推荐配套策略是:
- 调试阶段 `DISABLE_SYSTEM_SUSPEND=true`
- 菜单只保证运行态可用
后续如果要支持“近似待机但仍可双键唤出菜单”,再单独设计:
- sleeping 页面但不进真 suspend
### 9.2 菜单样式是 KUAL 风格,不是 KUAL 组件
本方案里的“KUAL 风格”是指:
- 白底
- 列表
- 简洁标题
- 文本选中态
并不是:
- 真的把 KUAL 的 Java/UI 组件拉起来
原因是当前 dashboard 已经停掉 framework直接复用 KUAL UI 的成本会更高。
### 9.3 第一版不依赖触摸
从评审角度看,第一版最好不要把选择动作建立在触摸上。
原因:
- 触摸设备事件会更复杂
- 点选区域映射也更容易出错
- 无 SSH 时问题更难恢复
所以第一版推荐:
- 只用翻页键完成整个菜单闭环
## 10. 后续验证路径
等 SSH 恢复后,建议按这个顺序验证:
1. 确认 `evtest /dev/input/event2` 仍能看到:
- `PageUp`
- `PageDown`
2. 先同步本地最新 dashboard 代码到 Kindle
- 本轮已在本地加入一个“短按 `power` 提前唤醒后,额外保持 60 秒”的改动
- 相关配置在 `dash/src/local/env.sh`
- `MANUAL_WAKE_KEEP_AWAKE_SECONDS=60`
- `MANUAL_WAKE_EARLY_TOLERANCE_SECONDS=5`
- 相关运行逻辑在 `dash/src/dash.sh`
3. 重启 dashboard先单独验证短按 `power`
- 从休眠态短按 `power`
- 预期不再只亮约 3 秒
- 预期应保持约 60 秒再回到下一轮休眠
4. 让 dashboard 保持运行态,不进真 suspend
5. 手动启动菜单服务
6. 验证:
- 双键能否稳定打开菜单
- 单键移动是否会误触组合键
- 再次双键能否稳定确认
7. 验证主题切换后:
- 背景立即更新
- 时钟位置和方向保持正确
## 11. 结论
在当前仓库和 Kindle Voyage 的约束下,推荐采用下面这条路线:
- 监听 `/dev/input/event2`
- 用短时间窗口识别 `PageUp + PageDown` 组合键
- 在运行态下绘制一个 KUAL 风格的文本主题菜单
-`PageUp / PageDown` 导航
- 再次双键确认
- 最终复用现有 `switch-theme.sh` 完成主题切换
这是当前成本最低、最容易恢复、也最适合在 SSH 不稳定阶段先落地评审的方案。

View File

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