Files
o62lte-customising-plan/s5e-charge/README.md
T
2026-10-03 00:01:12 +08:00

15 KiB
Raw Blame History

S5Chg —— 把 S5 41mm(q63) 的充电动画搬到 S5 eSIM 46mm

把 Xiaomi Watch S5 41mm(q63) vela_system.bin 里的 charging/charge0..60.bin(464×464), 逐帧等长覆盖写进 Xiaomi Watch S5 eSIM 46mm(p62lte) /dev/system 的同名 61 个槽位 (480×480,I8 + RLE)。

关系:s4e-charge 是「S5 41mm → S4 eSIM」的母项目(只读参考)。本项目沿用它的全部结构 (两遍构建回填偏移、指纹表拆盘、逐字节等长覆盖、独立校验),只把目标换成 S5 eSIM、 帧数改成 61→61、界面重做成 480×480、放大从"同尺寸"改成"464→480 放大"。

改了什么

目标 inputs/target/vela_system.bin(S5 eSIM 46mm /dev/system)→ charging/charge0..60.bin,61 帧 480×480 I8+RLE,槽位 143,832 ~ 213,511 B(各不相同)
源 inputs/source/vela_system.bin(q63 S5 41mm)→ charging/charge0..60.bin,61 帧 464×464
映射 帧号 1:1(n → n)。两边都是 61 帧,没有 S4e 那版的 round(n/56*60) 比例重采样
放大 源帧 RGB 用 Lanczos 放大到 480×480,再把每个像素吸附回源帧自己的 256 色调色板(最近的欧氏距离)→ 颜色零新增、零量化,空间上比最近邻放大平滑
本次 61/61 全换,每一帧都是"原调色板 + Lanczos"(没有一帧被降级:没有暗区合并、没有降色数、没有留原厂)
编码 压缩 8.69 MB,槽位共 11.27 MB → 还剩 2.57 MB 余量(最紧的一帧 charge47 也余 10,488 B)
校验 61/61 回解逐像素等于计划图、RLE 恰好解出 usize、inode 表/superblock 逐位不变、槽位外 0 字节变化、回放一致

等长的做法(硬约束,一步都不能省):解码源帧 → Lanczos 放大 480×480 → 吸附回源调色板 → LVGL RLE 压缩 → 用合法空指令补齐到原 csize。补齐用 00 00(重复 0 次)这类产出 0 像素 的指令;另外 usize_tail() 会在解码长度比 usize 少时重复最后一个字节补足 (踩过的坑:RLE 输出比 usize 少 1 个像素,设备端整帧不画)。

交付物

文件 字节数 id / b5 sha256
dist/S5Chg.face 11,986,752 462150103 / b5=0 577147341d363811965b51f46088ed98ea62c946cc8b7d205264577fab4d8066
dist/S5ChgRestore.face 11,986,752 462150103 / b5=0 8dbfc87acde8b54543fa7f2b54c1b0d7360634c83d6cf139a78df762ff078f2c

两份盘共用同一个表盘 id(462150103):装其中一份就会顶掉另一份,设备上始终只占一份空间。

  • S5Chg.face:pay = 替换后的 61 帧,rb = 原厂前 64 B 指纹(只给版本校验用)
  • S5ChgRestore.face:pay = 原厂61 帧(逐字节),rb = 替换后前 64 B 指纹

预览与报告:

  • docs/decode_preview.png —— 直接从交付物 .face 里解出来的 61 帧总览(最硬的证据)
  • docs/face_before_after.png —— 抽样 5 帧:上排 q63 原帧(464),下排交付物解出的帧(480)
  • docs/before_after.png —— 生成负载时的对照图(上=源帧,下=磁盘上改后镜像解出的帧)
  • docs/ui_*.png / docs/ui_all.png —— 480×480 界面(9 个规格页 + 8 个还原盘文案变体)
  • docs/selfcheck_report.txt —— tools/verify_all.py 的完整输出(33/33)
  • docs/selfcheck_run.txt —— 整条重建流水线的原始输出(8 步,逐步 EXIT=0)

真机怎么做

  1. 装 dist/S5Chg.face
  2. 表盘「快速帮助 → 环境预检查」(只读,一个字节都不写):确认 /dev/system 可读、 卷名是 system、61 条负载不越界,并对首/中/尾三条做抽样版本校验(原厂✓ / 已替换✓)
  3. 首页「开始替换」→ 确认页 → 确认替换 → 等 成功 61 失败 0(61 条 ≈ 11.3 MB)
  4. 「重启手表」→ 插上充电器看整段动画
  5. 还原:装 dist/S5ChgRestore.face →「开始还原」→ 重启

输出怎么读(「正在替换」页):每 10 条一行 > 写入 n/61 成功 x 失败 y;结束时 > 共 61 条,成功 x 失败 y。

判断成功的唯一标准是 成功 61 / 失败 0;如果出现 > 版本不符跳过 n 条,说明设备上的 /dev/system 不是算偏移时用的那一版固件 —— 此时不要重启,装对应版本的盘或先还原。

写入前每条都做版本校验:读设备上该偏移的前 64 B,必须等于「原厂前 64 B」或「替换后前 64 B」, 两边都不是就判「版本不符」并跳过 /dev/system 剩余记录(绝不把字节写进别人的文件里)。 写入后整条回读比对,失败自动重试 3 次 + 退化成整条一次 dd。

重建

# 需要 Python 3 + Pillow + numpy;inputs/ 与 vendor/ 要自己准备(见下)
python tools\make_charge_payload.py all      # 61 帧负载 + 整分区镜像(自带 4 项镜像级自证)
python tools\make_dd_payload.py replace      # 负载 + 指纹 + blob_replace/
python tools\gen_ui_lua.py replace           # 两遍构建 -> dist\S5Chg.face(0 漂移校验)
python tools\verify_anim.py replace          # 逐盘校验(11 项)
python tools\make_dd_payload.py restore
python tools\gen_ui_lua.py restore           # -> dist\S5ChgRestore.face
python tools\verify_anim.py restore
python tools\verify_all.py                   # 独立总校验(33 项)+ 预览图 + selfcheck_report.txt

或者一条命令跑完全部 8 步(逐步校验、任一步失败即中止,输出留档到 docs/selfcheck_run.txt):

& ".\tools\selfcheck.ps1"

make_charge_payload.py 不加 all 时只跑 [0,14,28,42,56] 这 5 帧(轻量试跑)。

界面相关(改了 tools/pages.py / main.lua 之后):

python tools\make_ui_images.py               # assets/ -> ui_images/*.bin(480 档尺寸)
python tools\render_ui.py                    # 480×480 预览(有标签溢框会直接报出来)
python tools\check_clip.py                   # 真实渲染像素判圆屏切边(0 越界才算过)

inputs/ 与 vendor/ 不进仓库

.gitignore 里已经排除(本项目 .gitignore 原样沿用,未改动):

路径 是什么
inputs/target/vela_system.bin 目标固件 p62lte 的 system 分区镜像(84,429,824 B)
inputs/source/vela_system.bin 源固件 q63 的 system 分区镜像(100,630,528 B)
inputs/fonts/MiSans-*.ttf 可选;只用于本机渲染预览(缺了就退回系统字体,只影响预览)
vendor/LuaDevTemplate-main/ 官方模板(含 Compiler.exe)—— 用默认路径即可,不要设 ICON_EDIT_VENDOR

固件分区镜像就是官方 OTA 包(ZIP)里的 vela_system.bin 条目,解压出来放到上表位置即可。

自证输出(全部真实执行过)

① 61 条负载长度逐条 == 原槽位长度

目标 S5 eSIM(p62lte) 61 帧(0..60,槽位合计 11.27 MB)
源   S5 41mm(q63)    61 帧(0..60,10.63 MB)
映射:n -> n(1:1)   输出分辨率:480x480(源 464x464 -> Lanczos 放大后吸附回源调色板)
   帧 47  <- q63 第 47  帧  槽位 144563 B  编码 134051 B(留白 10488)  ...  原调色板+Lanczos
回解自检通过 61 / 61(解码长度恰好 usize 且逐像素等于计划图)
编码方式分布:{'原调色板+Lanczos': 61}

tools/verify_all.py:[OK] replace:61 条长度 == 原厂槽位长度 不符 0 条 / [OK] restore:61 条长度 == 原厂槽位长度 不符 0 条; tools/verify_anim.py:pay 检查 61 条:长度全对 True

② 每条负载解出来都是 480×480(不能解码失败)

[OK] replace:61 条都解出 480x480 且 RLE 恰好 usize  不符 0 条
[OK] restore:61 条都解出 480x480 且 RLE 恰好 usize  不符 0 条

(usize 必须恰好 231,424 = 1024 + 480×480,多一字节设备端 INVALID、少一字节不画)

③ 改后整分区镜像的 inode 表 / superblock 逐位不变

inode 表逐位不变:True(2150 条)
superblock 一致:True(sb_size=84429200 / 84429200,sb_cksum=3275251810 / 3275251810)
逐字节差异:共 11585280 字节,全部落在 61 个槽位内:True(槽位外 0 字节)
[OK] inode 表逐位不变(2150 条) / [OK] superblock 逐位不变 / [OK] 除 61 个槽位外没有任何字节变化

④ 独立校验脚本全绿

=== tools/verify_all.py(只读 dist/*.face + inputs/*/vela_system.bin,不读 .work/)===
61 帧平均差:mean 0.189  max 0.260(第 17 帧)      <- 与"独立做的 Lanczos 放大"比对
最像的源帧号与自身不一致的帧:0 个                  <- 证明第 n 帧就是 q63 第 n 帧
[OK] replace 盘 rb == 原厂前 64 B(对固件镜像比对)  不符 0 条
[OK] restore 盘 pay == 原厂整条字节(逐字节)        不符 0 条
[OK] restore 盘 rb == replace 盘 pay 前 64 B(两份交付物互证)  不符 0 条
RESULT: 全部通过 —— 33 项通过,0 项失败

=== tools/verify_anim.py replace / restore ===
S5Chg:pay 61 条,rb 61 条      pay 检查:长度全对 True,可解码 True,字节一致 True
全部通过 —— 11 项通过,0 项失败            (restore 同样 11/11)

=== tools/check_clip.py(480×480 圆屏,含 replace/restore 两套文案)===
检查了 17 个页面(含 replace/restore 两套文案):越界页面 0
RESULT: PASS(0 越界、0 折行)

⑤ 两张 .face 的 sha256

S5Chg.face         11986752  b4=0 b5=0 id=b'462150103\x00'  sha256[:16]=577147341d363811
S5ChgRestore.face  11986752  b4=0 b5=0 id=b'462150103\x00'  sha256[:16]=8dbfc87acde8b545
S5Chg.face         sha256=577147341d363811965b51f46088ed98ea62c946cc8b7d205264577fab4d8066
S5ChgRestore.face  sha256=8dbfc87acde8b54543fa7f2b54c1b0d7360634c83d6cf139a78df762ff078f2c

完整重建 + 自证的原始输出(8 步全部 EXIT=0)留档在 docs/selfcheck_run.txt, 显式自证清单在 docs/selfcheck_report.txt(33/33)。重跑整条流水线:

& ".\tools\selfcheck.ps1"        # = 上面 8 条命令,逐步校验,任一步失败即中止

已知边界 / 没做的部分

  1. 没有一帧颜色数被降级,也没有一帧保留原厂:61/61 都是「原调色板 + Lanczos」。 全帧色数 251~256(即源调色板的实际用量),吸附误差 0.189/255(最大 0.260,第 17 帧)。
  2. 没做锐化:放大只用 Lanczos + 调色板吸附。这是刻意的 —— 锐化会增加索引噪点、 直接吃掉 RLE 余量(最紧的 charge47 只剩 10,488 B)。
  3. 真机未验证 S5 eSIM 的 /dev/system 可写:这一点在 S4 eSIM 上实测通过(同源平台、 同一套 dd 流程),S5 eSIM 上只能先靠只读的「环境预检查」确认可读 + 偏移对版。 system/drain/*、ota/anim*、setupwizard/* 同理可改,但同样未在本机验证。
  4. 偏移锁固件版本:换固件必须重跑 make_charge_payload.py all → make_dd_payload.py → gen_ui_lua.py。用错版本时表盘会判「版本不符」并跳过 /dev/system 剩余记录, 不会把字节写进别人的文件里。
  5. Compiler.exe 的设备表里没有 480 档(枚举 DeviceType 只有 17 个已知值,圆表且预览 326×326 的只有 362 / 462)→ 这里用 DeviceType=462 + 326×326 预览图,预览只是表盘列表 里的缩略图,与实际 480×480 画面无关(界面尺寸由 main.lua 与 .fprj 的 480 决定)。
  6. 负载文件必须原样连续放在 resource.bin 里,偏移才等于 文件基址 + 魔数长度 + 相对偏移; gen_ui_lua.py 会断言这一点并做两遍 0 漂移校验。魔数长度是 15 B(曾把 15 写成 16 导致全部偏移差 1)。
  7. 写入速度:偏移都是 512 对齐,所以 195 KB 的记录按 bs=512 + bs=4 + bs=1 三段写, 不是逐字节;sync + 整条回读比对,失败重试 3 次后改用整条一次 dd。
  8. 没动的:inode 表、目录项、superblock、任何非 charging/charge*.bin 的文件; 仓库里其它项目(s4e-*、s5e-icons、s5e-pressure-anim)与 git(未 commit、未改 .gitignore)。

目录

main.lua                 表盘源码(含 __UIIMG__ / __PAY__ / __RB__ / __ROLE__ 占位符,构建时回填)
tools/                   全部脚本(见下表)
ui_images/               界面素材(LVGL v9 .bin,8 张,480 档尺寸)
assets/                  设计素材 PNG
dist/                    交付的 .face
docs/                    预览图、对照图、selfcheck_report.txt
inputs/                  固件分区镜像 + 字体(不进仓库,见上)
vendor/                  LuaDevTemplate-main(含 Compiler.exe,不进仓库)
.work/                   构建工作区(产物,可删)
脚本 作用 相对 s4e-charge
tools/paths.py 所有路径的唯一入口 沿用(未改)
tools/romfs.py ROMFS 分区解析 沿用(未改)
tools/lvgl_bin.py LVGL v9 RLE 编解码 + pad_rle_safe 沿用(未改)
tools/iconpack.py ARGB8888/I8 图片编码(界面素材用) 沿用(未改)
tools/lua_balance.py Lua 块配平检查 沿用(未改)
tools/make_charge_payload.py 改:目标/源换成 S5e + q63,61→61(n→n),输出 480×480,Lanczos+调色板吸附 重写
tools/make_dd_payload.py 改:魔数 S5C-PAYv1 / S5C-FPv1,脚本文案与设备名 小改
tools/make_overlays.py 改:DeviceType 462 / Screen 480 / 预览 326(按首页规格真渲染) 改
tools/build_faces.py 改:盘名 S5Chg / S5ChgRestore,id 462150103 小改
tools/finalize_faces.py 改:同上(b5=0 的忠实产物 + 改 id) 小改
tools/gen_ui_lua.py 改:盘名/角色名 小改
tools/verify_anim.py 改:盘名 + 480×480 判据 小改
tools/verify_all.py 新增:独立总校验(33 项,不读 .work/)+ 预览图 + 报告 新增
tools/selfcheck.ps1 新增:一条命令跑完上面 8 步(逐步校验、失败即中止、输出留档) 新增
tools/pages.py 改:480×480 页面规格(含还原盘文案变体) 重写
tools/render_ui.py 改:画布 466→480,输出到 docs/,报标签溢框 小改
tools/check_clip.py 改:圆屏 240/240,两套文案都过一遍 改
tools/make_ui_images.py 改:素材尺寸换 480 档(31/18×29/80) 改
main.lua 改:480 几何、S5Chg 文案、版本不符跳过分区剩余记录 重写