Files
o62lte-customising-plan/s5e-icons
2026-10-02 21:34:56 +08:00
..
2026-10-02 21:34:56 +08:00
2026-10-02 21:34:56 +08:00
2026-10-02 21:34:56 +08:00
2026-10-02 21:34:56 +08:00
2026-10-02 21:34:56 +08:00
2026-10-02 21:34:56 +08:00
2026-10-02 21:34:56 +08:00
2026-10-02 21:34:56 +08:00
2026-10-02 21:34:56 +08:00
2026-10-02 21:34:56 +08:00

S5 图标包 → Xiaomi Watch S5 eSIM 46mm(图标修改器 · Lua 表盘)

把 Xiaomi Watch S5 41mm(q63, v4.101.020)的 OS4 图标包写进 Xiaomi Watch S5 eSIM 46mm(p62lte, v3.112.035) 的 /dev/app 与 /dev/health 两个分区。

  • 153 条负载:47 个 launcher 图标(app 33 + health 14)+ 44 个系统 UI 素材(8 个锁屏/通知/免打扰小图标 + 22 张控制中心磁贴 + 14 张压力 App 小表情)+ 160 张对讲机素材(walkie_talkie/**)→ 全部等长覆盖,inode 表与后面所有偏移一个字节都不动
  • 长宽比不一致的素材一律不替换(保持原厂):源图与槽位长宽比相差 >0.5% 就跳过 —— 拉伸会把方块变长方形、裁切会切掉按钮两端。所以 walkie_talkie/image/stop.bin(176×96 vs 144×96)与 walkie_talkie/guidance/finish_guidance.bin(176×96 vs 160×96)保持原厂(本机逐字节确认)
  • 对讲机那一组是 I8+RLE 压缩槽位(固件里 flags=0x08,长度不是 4 的倍数)→ iconpack.encode_into_slot():量化(颜色数从 256 逐级往下试)→ RLE → 用 ctrl=0 的 2 字节空指令补齐到原 csize,做到逐字节等长;设备端写入这类记录自动退化成 dd bs=1
  • 界面按 480×480 重做(设计稿在 tools/s5e_pages.py,本机用真 MiSans 渲染 + 真实像素判圆屏切边,7 页 0 越界)
  • 压力 App 小表情也搬了 14 张(pressure/icon/):4 档情绪 48px(relax/mild/mid/severe)+ 32px 三张 + press_grade1..4 等级徽标 + presure_icon + 两张表情底版(pressure_eomji_bg / _widbg);96px 那四张两边本来就一样(自动跳过)。没搬的:整屏背景(pressure_bg/success_bg/success_arc/press_widget_bg*/press_chart_bg)、reminder.bin、pressure/measure/* 动画帧(体积大)
  • 系统 UI 素材整套一起搬:8 个锁屏/通知/免打扰小图标 + 22 张控制中心磁贴(q63 磁贴是 116×116 浅色主题,缩放到 S5 eSIM 的 128×128)。白盘放在深色控制中心上的观感已在 S4 eSIM 上实测,效果可以
  • 只读预检查 + 每条写入前后对版/回读校验 + 一键回滚

1. 交付物与安装

文件 说明
dist/S5eIcons.face 主交付物 10,852,268 B(sha256 50da912734a5db48…),表盘 id 462150101,b5=0(与固件自带表盘一致)
dist/S5eIcons_toolchain.face 官方工具链原样产物(b5=10);上面那个装不上时换这个
.work/s5e_from_os4/vela_app_os4.bin 重建后得到的整分区镜像(可直接刷机,绕开设备侧脚本)
.work/s5e_from_os4/vela_health_os4.bin 同上(health 分区)
docs/说明.md 完整说明:固件事实、47 条偏移表、验证结果、待实测项
docs/s5e_before_after.png 47 个 launcher 图标:原厂 → 替换后 对照图
docs/s5e_sysicons.png 44 个系统 UI 素材(22 控制中心 + 14 压力表情 + 8 其他):原厂 → 从 S5 41mm 搬过来的 对照图
docs/s5e_pressure.png 14 张压力 App 小表情:原厂 → q63 对照图
docs/s5e_ui_all.png 480×480 七个页面预览

装法与普通 Lua 表盘相同(resource.bin 推到 /data/app/watchface/market/<id>/)。装好后在表盘上操作:

首页 →「快速帮助」→「环境预检查」   只读,一个字节都不写
首页 →「开始替换」→ 确认 → 自动写入 153 条 →「重启手表」
       快速帮助 →「回滚修改」→ 确认 → 写回原厂图标

输出怎么读(「正在替换」页):每 10 条一行 > n/153 成 x 失 y;结束时 > 成功 x / 失败 y(共 153)、必要时 > 版本不符跳过 n 条,以及 > app/health 分区分条统计。

**判断成功的唯一标准是 失败 0。**不是 0 就先别重启,点「回滚修改」。


2. 目录

main.lua                 表盘源码(含 __UIIMG__ / __PAY__ / __RB__ 占位符,构建时回填)
payload/                 payload.bin = MAGIC + 91 条顺序拼接;payload.lua = 逐条偏移表
payload_rollback/        原厂字节(回滚用)
ui_images/               界面素材(LVGL v9 .bin,8 张)
assets/                  设计素材 PNG(Mdi 图标、返回箭头、QQ、封面)
assets/override/         自制图覆盖(activities.png = 跑道图标,优先级高于固件源)
tools/                   全部脚本(见下表)
dist/                    交付的 .face
docs/                    说明与预览图
inputs/                  要你自己放固件分区镜像(见第 4 节,不进仓库)
vendor/                  放第三方 LuaDevTemplate-main(不进仓库)
.work/                   构建工作区(产物,可删)
脚本 作用
tools/paths.py 所有路径的唯一入口(inputs / vendor / .work / dist,支持环境变量覆盖)
tools/s5e_payload.py 从两个分区镜像生成 91 条负载 + 回滚 + 整分区镜像(自带正反两个证明)
tools/s5e_ui_images.py 设计素材 PNG → LVGL .bin + 圆形 logo
tools/s5e_pages.py 480×480 页面规格(唯一的设计稿来源)
tools/s5e_render_ui.py 用真 MiSans 渲染 7 页预览
tools/s5e_check_clip.py 用真实渲染像素判圆屏是否切边
tools/s5e_build.py 两遍构建 → dist/S5eIcons.face(自动回填偏移 + 0 漂移校验)
tools/s5e_verify.py 独立校验:从 .face 里反解 Lua 表与偏移,再与固件镜像逐字节比对
tools/romfs.py tools/lvgl_bin.py tools/iconpack.py tools/render_ui.py tools/lua_balance.py 共用底层(ROMFS 解析 / LVGL 图片编解码 / UI 渲染 / Lua 块平衡)

3. 目标固件的事实(从固件包里查出来的)

项 结论 证据
机型 Xiaomi Watch S5 eSIM 46mm vela_ap.bin / vela_cp_image.bin 里的机型字符串
屏幕 480×480 自带表盘素材 bigsun.bin、citymage1.bin 都是 I8 480×480(231,436 B);complexzone.lua 建 480×480 根对象
Lua 宿主 有,Lua 5.4 vela_ap.bin 里 Lua 5.4、SCRIPT_PATH、watchface.db、luavgl,以及同样的 7 个 C 模块 luaopen_activity/dataman/miwear/navigator/screen/topic/vibrator
自带 Lua 表盘 7 个 outdooradv / daynight / corona / complexzone / clearheart / circle / asymmetryzone
图标槽位 /dev/app 34 个 + /dev/health 15 个 ROMFS app / health 卷里的 */launcher.bin
图标格式 全部 144×144 ARGB8888 = 82,956 B 逐个解码确认;这台没有 I8 槽位(S4 eSIM 有 6 个),不存在量化损失
原厂画法 内容 136×136 居中(4px 透明边);launcher_* 138@(3,3);debug / lua / voice_alexa / sports_game 满幅 144 逐像素量 alpha 边界

4. 替换内容

  • 能对上 47 个槽位(app 33 + health 14);OS4 里没有对应的 2 个槽位保持原厂:walkie_talkie、sports_game
  • 其中 8 个槽位的 OS4 图标与原厂逐字节相同(debug / esimsms / interconnect / lua / sports / sports_course / sports_record / sports_training),照样写入,保证整套一致
  • activities 用自制图覆盖:assets/override/activities.png(112×112 红/绿/青同心环,即 S4 项目里那张跑道图标)优先于 q63 固件源。裁剪 = 取实心 bbox(100×100) → 放大到目标槽位画法(136×136,LANCZOS + 锐化) → 居中粘回 (4,4);实测成品外径与原厂图标完全对齐(都是 (4,4)-(139,139))
  • voice_aivs(小爱同学)同样用自制图覆盖:assets/override/voice_aivs.png(用户给的 144×144 PNG,圆形白盘直径 140)→ 裁到实心 → 缩放到槽位画法 136×136 → 居中;编码后 82,956 B 与原槽位等长
  • 尺寸归一化:把 OS4 的满幅 144 图按目标槽位的画法缩放(多数 136、launcher_* 138)并居中到同一原点 —— 这样每条负载长度与原槽位完全相等
  • 负载 4.41 MB(替换 + 回滚各一份),.face 共 8.98 MB

5. 重建

5.1 需要自己准备的输入(放进 inputs/,不进仓库)

路径 是什么 大小 / sha256 前 16 位
inputs/target/vela_app.bin 目标固件(p62lte)app 分区 103,043,072 / 9b41c5f5eb17c112
inputs/target/vela_health.bin 目标固件 health 分区 206,924,800 / 489752c8a3fb24ff
inputs/target/vela_font.bin 目标固件 font 分区(可选:渲染预览时从里面取 MiSans) 43,379,712 / 16cb803ac1bd74c5
inputs/source/vela_app.bin 图标来源(S5 41mm q63)app 分区 206,000,128 / 161a8f7a5f0bd8af
inputs/source/vela_health.bin 图标来源 health 分区 233,939,968 / 12a3bd2f8b62e969
inputs/fonts/MiSans-Medium.ttf、MiSans-Semibold.ttf 可选;不提供就自动从 inputs/target/vela_font.bin 里取 —
vendor/LuaDevTemplate-main/ 官方模板(里面有 Compiler.exe) —

固件分区镜像就是官方 OTA 包(ZIP)里的 vela_*.bin 条目,解压出来即可。

5.2 步骤

# 需要 Python 3 + Pillow
python tools/s5e_payload.py      # 负载 + 整分区镜像(自己证明 正放=改后镜像 / 反放=原厂镜像)
python tools/s5e_ui_images.py    # 界面素材
python tools/s5e_render_ui.py    # 480 预览(可选)
python tools/s5e_check_clip.py   # 圆屏切边检查(可选)
python tools/s5e_build.py        # 两遍构建 -> dist/S5eIcons.face
python tools/s5e_verify.py       # 独立校验

只想改界面/坐标:改 main.lua 与 tools/s5e_pages.py,然后跑 s5e_render_ui.py + s5e_check_clip.py + s5e_build.py + s5e_verify.py。


6. 已知边界(踩过的坑)

  1. 对讲机素材默认不打包(ICON_WALKIE=1 可打开);160 张 PNG 仍在 ssets/walkie_talkie/。

  2. 偏移锁固件版本:换固件要重跑 s5e_payload.py + 构建。用错版本时表盘会判「版本不符」整分区跳过,不会把字节写进别人的文件里。

  3. Compiler.exe 的设备表里没有 480 档(枚举 DeviceType 1..1500 只有 17 个已知值,圆表且预览 326×326 的只有 362 / 462)→ 这里用 462 + 326×326 预览。预览只是表盘列表里的缩略图。

  4. 表盘宿主能不能写 /dev/app、/dev/health 未在真机验证(S4 eSIM 上写 /dev/app 是实测通过的,同源平台)。所以界面里第一步是只读的「环境预检查」。

  5. 负载文件必须原样连续放在 .face 里,偏移才等于 文件基址 + 魔数长度 + 相对偏移;s5e_build.py 会断言这一点。魔数长度不要手写常量(曾经把 15 写成 16,全部偏移差 1 字节)。

  6. 独立校验不是摆设:tools/s5e_verify.py 从 .face 里的 Lua 表反解偏移再与固件镜像比对,本项目开发时它抓出两个真 bug —— 魔数长度差 1;Lua 表里存的是 /dev/app 而不是运行时期望的 app 键(会让 dd 拼成 if=nil)。

  7. 负载写入走 /tmp 中转(resource.bin 内的偏移不保证 4 字节对齐,不能直接 dd skip)。

7. 第三方与风险

  • 本仓库不含固件、OTA 包、字体;.face 由第三方 Compiler.exe 编译(见 ../README.md)
  • 往分区节点写数据是危险操作:写错偏移可能损坏资源分区。表盘自带版本校验与回滚,但请自行确认设备所有权并承担后果
  • 官方 OTA 升级会整分区覆盖,图标会回到原厂(等于自动还原)