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: 补充界面语言、版本机制与协议通告布局的说明
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
#!/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())
|
||||
Reference in New Issue
Block a user