feat(client): RMIC::2026 界面重做、可计算的版本机制与固件版本联动
构建客户端 / 检查(类型 + 单元测试) (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): 界面换成墨黑蓝底 + 单支红的矩形语言,切角全部去掉,仅装甲条保留平行四边形
- 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:
2026-10-19 02:40:08 +08:00
parent cc6b82d7ec
commit 896b21fe44
332 changed files with 16729 additions and 2765 deletions
+138
View File
@@ -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())