Files
ESP32S3_vt/scripts/make_bundle.py
T
sloray 896b21fe44
构建客户端 / 检查(类型 + 单元测试) (push) Canceled after 0s
构建客户端 / Linux x64(deb + AppImage) (push) Canceled after 0s
构建客户端 / Windows x64(NSIS 安装包) (push) Canceled after 0s
构建客户端 / macOS 通用(.app + .dmg) (push) Canceled after 0s
feat(client): RMIC::2026 界面重做、可计算的版本机制与固件版本联动
- feat(client): 界面换成墨黑蓝底 + 单支红的矩形语言,切角全部去掉,仅装甲条保留平行四边形
- feat(client): 自托管 HarmonyOS Sans SC 与 JetBrains Mono,中文与拉丁数字逐字回退
- feat(client): 新增启动序列、首启门、用户端 HUD、裁判端控制台、设置抽屉与固件维护页
- feat(client): 裁判端中缝与下方控制台可拖,鼠标落在哪一路下方就看哪一路
- feat(client): 设置抽屉常驻实时预览,与画面共用同一份准星绘制代码
- feat(client): 动效改用 Material 3 的缓动曲线与时长,逐条替换掉自造的贝塞尔
- feat(version): 版本号与 CHANGELOG 由 scripts/version.mjs 从 git 历史算出,不再手写
- feat(firmware): Hello 报文携带语义化版本、构建号与自报协议号,负载向后兼容
- feat(client): 固件版本显示在绑定列表与裁判端每一格;协议不匹配时给出具体原因
- feat(firmware): 固件包版本与客户端同源,都从 version.json 读
- fix(client): getCurrentWindow() 在没有 Tauri 注入时同步抛异常,浏览器预览整棵树被卸载
- fix(client): 启动序列的淡出 effect 清掉了自己刚设的 setTimeout,界面永远停在启动页
- fix(client): 设置抽屉左侧导航的锚点没挂到 section 上,且用错了坐标系
- fix(protocol): Hello 改用宽松帧头解析,让协议不匹配的板子能被解释而不是静默消失
- fix(build): Windows 上 bindgen 加载不了 libclang 的同目录依赖,构建脚本补上搜索路径
- fix(build): esp-idf-sys 在长路径下拒绝构建,固件构建改用短的目标目录
- build(client): 队标图标,含光学尺寸分级与手工拼的多尺寸 ICO
- build(ci): 守门 job 增加版本一致性检查,产物名改为 rmic-2026-*
- docs: 补充界面语言、版本机制与协议通告布局的说明
2026-10-19 02:40:08 +08:00

139 lines
4.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""把一个已构建的固件目录封装成客户端认的官方 bundle。
官方 bundle 是一个目录,至少包含:
bundle.json
esp32s3-app.bin
partition-table.bin
bootloader.bin # 可选,客户端默认不写
客户端会先校验 bundle.json 里声明的 SHA256,再读设备当前分区表、选择空闲槽位、
写 App + 整段回读;只有回读通过才切换分区表。它不会按这里的 partition-table.bin
直接覆盖设备分区表——设备上的 NVS/PHY 布局以设备当前表为准。
用法(在仓库根目录):
python scripts/make_bundle.py --firmware target/firmware
可选参数:
--name "ESP32-S3 图传固件"
--version "..." 默认读 BUILD-INFO.json 的 build_variant+source_revision
--app-partition-size 0x140000
--flash-size 16mb --flash-mode dio --flash-frequency 40mhz
--notes "..."
"""
from __future__ import annotations
import argparse
import hashlib
import json
import sys
from pathlib import Path
def sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as handle:
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def read_build_info(firmware: Path) -> dict:
path = firmware / "BUILD-INFO.json"
if not path.exists():
return {}
try:
return json.loads(path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError):
return {}
def main() -> int:
parser = argparse.ArgumentParser(description="生成官方固件 bundle.json")
parser.add_argument("--firmware", default="target/firmware", help="固件产物目录")
parser.add_argument("--name", default="ESP32-S3 图传固件")
parser.add_argument("--version", default="")
parser.add_argument("--app", default="esp32s3-app.bin")
parser.add_argument("--partition-table", default="partition-table.bin")
parser.add_argument("--bootloader", default="bootloader.bin")
parser.add_argument("--app-partition-size", default="0x140000")
parser.add_argument("--flash-size", default="16mb")
parser.add_argument("--flash-mode", default="dio")
parser.add_argument("--flash-frequency", default="40mhz")
parser.add_argument("--chip", default="esp32s3")
parser.add_argument("--notes", default="官方 bundle:客户端只写 App 与 patch 后的分区表,不重写 bootloader;旧 App/分区表会先备份。")
args = parser.parse_args()
firmware = Path(args.firmware)
if not firmware.is_dir():
print(f"固件目录不存在:{firmware}", file=sys.stderr)
return 1
app = firmware / args.app
partition_table = firmware / args.partition_table
bootloader = firmware / args.bootloader
for required, label in ((app, "App"), (partition_table, "分区表")):
if not required.is_file():
print(f"{label}文件不存在:{required}", file=sys.stderr)
return 1
if partition_table.stat().st_size != 0xC00:
print(
f"分区表应为 0xC00 字节,实际 {partition_table.stat().st_size}",
file=sys.stderr,
)
return 1
build = read_build_info(firmware)
version = args.version
if not version:
variant = build.get("build_variant")
revision = build.get("source_revision")
if variant and revision:
version = f"{variant}+{revision[:7]}"
else:
version = "0.1.0"
manifest = {
"format": 1,
"name": args.name,
"version": version,
"chip": args.chip,
"app": args.app,
"appSha256": sha256(app),
"partitionTable": args.partition_table,
"partitionTableSha256": sha256(partition_table),
"appPartitionSize": args.app_partition_size,
"flashSize": args.flash_size,
"flashMode": args.flash_mode,
"flashFrequency": args.flash_frequency,
"notes": args.notes,
}
if bootloader.is_file():
manifest["bootloader"] = args.bootloader
manifest["bootloaderSha256"] = sha256(bootloader)
else:
print(f"提示:没有 {bootloader},bundle 将不包含 bootloader", file=sys.stderr)
output = firmware / "bundle.json"
output.write_text(
json.dumps(manifest, ensure_ascii=False, indent=2) + "\n",
encoding="utf-8",
)
print(f"已写入 {output}")
print(f" {manifest['name']} {manifest['version']}")
print(f" App {app.name} · {app.stat().st_size} 字节 · {manifest['appSha256']}")
print(f" App 分区 {manifest['appPartitionSize']} · Flash {manifest['flashSize']}")
return 0
if __name__ == "__main__":
raise SystemExit(main())