K1Guitar/tools/publish_k1_release.py

215 lines
8.4 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.

# -*- coding: utf-8 -*-
"""Build K1 release zip: Boot + MCU APP + ExtFlash resources + SoundWalkerIAP + docs.
External Flash has TWO regions that must both be present on a blank/erased chip:
0x00000000 tone pack (logo pad + Charg + 1/2/3.bin) -> *.tone.res / combined
0x00100000 UI0902 bitmaps (mode rows, boot logo, ...) -> *.ui0902.res / combined
Prefer flashing the combined *.extflash.res @ 0x0 for colleague upgrades.
Boot must also be updated so USB IAP wait screen can show UI0902_FLASH_MODE.
"""
from __future__ import annotations
import hashlib
import shutil
import subprocess
import zipfile
from datetime import datetime
from pathlib import Path
REPO = Path(r"C:\Users\qjyu\Documents\SoundWalker\一诺国际吉他")
PROJ = REPO / "Code" / "YNGJ-GT1-M - AT32F403ARCT7"
EXE_BIN = PROJ / "project" / "IAR_V7.4" / "YNGJ-GT1-M" / "Exe" / "YNGJ-GT1-M.bin"
BOOT_BIN = (
PROJ
/ "AT32F403ARCT7_BOOT"
/ "project"
/ "IAR_V7.4"
/ "AT32F403ARCT7_BOOT"
/ "Exe"
/ "AT32F403ARCT7_BOOT.bin"
)
TONE_BIN = PROJ / "tools" / "out" / "extflash_tone_0903.bin"
UI0902_BIN = PROJ / "tools" / "out" / "ui0902_res.bin"
MAP_TXT = REPO / "Doc" / "音色文件" / "0903" / "FLASH_MAP.txt"
IAP_DIR = REPO / "升级" / "MCU主控升级"
OUT_ROOT = REPO / "tools" / "out"
FW_VER = "0.2.6"
TONE_RES_VER = "0903"
UI0902_RES_BASE = 0x00100000
BOOT_FLASH_ADDR = 0x08000000
APP_FLASH_ADDR = 0x08008000
def git_info(cwd: Path) -> tuple[str, str]:
def run(args: list[str]) -> str:
r = subprocess.run(
args, cwd=str(cwd), capture_output=True, text=True, encoding="utf-8", errors="replace"
)
return (r.stdout or "").strip()
commit = run(["git", "rev-parse", "--short=7", "HEAD"]) or "nogit"
subject = run(["git", "log", "-1", "--pretty=%s"]) or ""
return commit, subject
def sha256(path: Path) -> str:
h = hashlib.sha256()
with path.open("rb") as f:
for chunk in iter(lambda: f.read(1 << 20), b""):
h.update(chunk)
return h.hexdigest()
def build_combined_extflash(tone: bytes, ui: bytes) -> bytes:
if len(tone) > UI0902_RES_BASE:
raise SystemExit(f"tone pack {len(tone)} overflows into UI0902 @ 0x{UI0902_RES_BASE:X}")
pad = UI0902_RES_BASE - len(tone)
return tone + (b"\xFF" * pad) + ui
def verify_tone_layout(tone: bytes) -> None:
"""Refuse to ship ALL.res if BIN3 is missing/shifted (2.bin length drift)."""
dab = b"\xABDAB"
checks = [
(0x0001B8F0, 31, "BIN1"),
(0x0009D07D, 1, "BIN2"),
(0x000A71AC, 3, "BIN3"),
]
for off, want_cnt, name in checks:
if off + 16 > len(tone):
raise SystemExit(f"verify {name}: tone pack too short for 0x{off:X}")
magic = tone[off : off + 4]
cnt = int.from_bytes(tone[off + 12 : off + 16], "little")
if magic != dab or cnt != want_cnt:
raise SystemExit(
f"verify {name} @0x{off:X} failed: magic={magic.hex()} cnt={cnt} "
f"(want ABDAB/{want_cnt}). Re-run pack_extflash_tone_0903.py; "
f"do not publish a pack where 2.bin length shifted 3.bin."
)
print("verify tone layout: BIN1/BIN2/BIN3 DAB OK at fixed FW addresses")
def main() -> None:
if not BOOT_BIN.is_file():
raise SystemExit(f"missing Boot bin: {BOOT_BIN}")
if not EXE_BIN.is_file():
raise SystemExit(f"missing MCU bin: {EXE_BIN}")
if not TONE_BIN.is_file():
raise SystemExit(f"missing tone pack: {TONE_BIN} (run pack_extflash_tone_0903.py)")
if not UI0902_BIN.is_file():
raise SystemExit(f"missing UI0902 pack: {UI0902_BIN} (run gen_ui0902_assets.py)")
iap = IAP_DIR / "SoundWalkerIAP.exe"
guide = IAP_DIR / "升级步骤.docx"
if not iap.is_file():
raise SystemExit(f"missing IAP tool: {iap}")
if not guide.is_file():
raise SystemExit(f"missing guide: {guide}")
stamp = datetime.now().strftime("%Y%m%d")
commit, subject = git_info(PROJ)
base = f"K1_MCU_v{FW_VER}_toneRes_v{TONE_RES_VER}_{stamp}_{commit}"
OUT_ROOT.mkdir(parents=True, exist_ok=True)
pkg = OUT_ROOT / base
if pkg.exists():
shutil.rmtree(pkg)
pkg.mkdir(parents=True)
tone_data = TONE_BIN.read_bytes()
verify_tone_layout(tone_data)
ui_data = UI0902_BIN.read_bytes()
combined = build_combined_extflash(tone_data, ui_data)
verify_tone_layout(combined) # same offsets in ALL.res
boot_name = f"AT32F403ARCT7_BOOT_v{FW_VER}_{stamp}.bin"
mcu_name = f"YNGJ-GT1-M_MCU_v{FW_VER}_{stamp}.bin"
comb_name = f"extflash_ALL_tone0903_ui0902_{stamp}.res"
boot_dst = pkg / boot_name
mcu_dst = pkg / mcu_name
comb_dst = pkg / comb_name
shutil.copy2(BOOT_BIN, boot_dst)
shutil.copy2(EXE_BIN, mcu_dst)
comb_dst.write_bytes(combined)
shutil.copy2(iap, pkg / "SoundWalkerIAP.exe")
shutil.copy2(guide, pkg / "升级步骤.docx")
if MAP_TXT.is_file():
shutil.copy2(MAP_TXT, pkg / "FLASH_MAP.txt")
shutil.copy2(boot_dst, OUT_ROOT / boot_name)
shutil.copy2(mcu_dst, OUT_ROOT / mcu_name)
shutil.copy2(comb_dst, OUT_ROOT / "extflash_ALL_tone0903_ui0902.res")
# Keep build intermediates under tools/out for local rebuilds; not shipped in package.
(OUT_ROOT / "extflash_tone_0903.res").write_bytes(tone_data)
(OUT_ROOT / "extflash_ui0902.res").write_bytes(ui_data)
readme = pkg / "README.txt"
readme.write_text(
"\n".join(
[
f"K1 release {base}",
f"Built {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
f"Git HEAD {commit}",
f"Git subject {subject}",
f"FW version {FW_VER}",
"",
"==== 整机升级请刷这三项 ====",
f" 1) {boot_name} -> Bootloader @ 0x{BOOT_FLASH_ADDR:08X}",
f" 2) {mcu_name} -> MCU APP @ 0x{APP_FLASH_ADDR:08X}",
f" 3) {comb_name} -> 外部 Flash 从 0x0 起整包",
" (= 音色区 + 填充 + UI0902 图片区)",
"",
"建议步骤:",
" - 先刷 Boot否则 USB 烧录等待画面仍是旧红字「升级模式」)",
" - 再刷 APP",
" - 先整片擦除外部 Flash再刷上述 ALL .res @ 0x00000000",
" - 勿只刷音色区否则模式选择页会花屏UI 图在 0x100000",
"",
"外部 Flash 分区:",
" 0x00000000 音色/充电图 (toneRes)",
" logo pad + Charg@0xCB70 + 1.bin@0x1B8F0 + 2.bin@0x9D07D + 3.bin@0xA71AC(FIXED)",
" 0x00100000 UI0902 图片 (模式选择、开机 Logo、充电、烧录模式、设置/调音台等)",
" 0x001D2000 UI0902_FLASH_MODE 烧录模式全屏图Boot 读取)",
"",
f" {boot_name} size={boot_dst.stat().st_size} @ 0x{BOOT_FLASH_ADDR:08X}",
f" sha256={sha256(boot_dst)}",
f" {mcu_name} size={mcu_dst.stat().st_size} @ 0x{APP_FLASH_ADDR:08X}",
f" sha256={sha256(mcu_dst)}",
f" {comb_name} size={comb_dst.stat().st_size} @ 0x00000000",
f" sha256={sha256(comb_dst)}",
"",
"说明:",
" - Boot 进入 USB IAP 时显示 UI0902_FLASH_MODE资源未烧录时白字黑底兜底。",
" - 烧录模式图来源K1标准界面图 0904/烧录模式.png。",
" - 开机全屏 Logo、关机充电全屏画面在 UI0902不在音色包里的 logo.bin/Charg.bin 占位。",
" - AutoBand 0x9EB5F 本版未重排。",
"",
"附件: SoundWalkerIAP.exe / 升级步骤.docx / FLASH_MAP.txt",
"",
]
),
encoding="utf-8",
newline="\n",
)
zip_path = OUT_ROOT / f"{base}.zip"
if zip_path.exists():
zip_path.unlink()
with zipfile.ZipFile(zip_path, "w", compression=zipfile.ZIP_DEFLATED) as zf:
for f in sorted(pkg.rglob("*")):
if f.is_file():
zf.write(f, arcname=f"{base}/{f.relative_to(pkg).as_posix()}")
print(f"PKG {pkg}")
print(f"ZIP {zip_path} ({zip_path.stat().st_size} bytes)")
for f in sorted(pkg.iterdir()):
print(f" {f.name} {f.stat().st_size}")
if __name__ == "__main__":
main()