136 lines
4.7 KiB
Python
136 lines
4.7 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Build K1 release zip: MCU .bin + tone .res + SoundWalkerIAP + docs.
|
|
|
|
No RTT flashing. Version stays Firmware Version[] (currently 0.2.0).
|
|
"""
|
|
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"
|
|
TONE_BIN = PROJ / "tools" / "out" / "extflash_tone_0903.bin"
|
|
MAP_TXT = REPO / "Doc" / "音色文件" / "0903" / "FLASH_MAP.txt"
|
|
IAP_DIR = REPO / "升级" / "MCU主控升级"
|
|
OUT_ROOT = REPO / "tools" / "out"
|
|
|
|
FW_VER = "0.2.0"
|
|
TONE_RES_VER = "0903"
|
|
|
|
|
|
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 main() -> None:
|
|
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)")
|
|
|
|
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}")
|
|
|
|
# date + commit only (no minutes, no dirty tag)
|
|
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)
|
|
|
|
mcu_name = f"YNGJ-GT1-M_MCU_v{FW_VER}_{stamp}.bin"
|
|
res_name = f"extflash_toneRes_v{TONE_RES_VER}_{stamp}.res"
|
|
mcu_dst = pkg / mcu_name
|
|
res_dst = pkg / res_name
|
|
shutil.copy2(EXE_BIN, mcu_dst)
|
|
shutil.copy2(TONE_BIN, res_dst)
|
|
shutil.copy2(iap, pkg / "SoundWalkerIAP.exe")
|
|
shutil.copy2(guide, pkg / "升级步骤.docx")
|
|
if MAP_TXT.is_file():
|
|
shutil.copy2(MAP_TXT, pkg / "FLASH_MAP.txt")
|
|
|
|
# flat convenience copies
|
|
shutil.copy2(res_dst, OUT_ROOT / "extflash_tone_0903.res")
|
|
shutil.copy2(mcu_dst, OUT_ROOT / mcu_name)
|
|
|
|
readme = pkg / "README.txt"
|
|
readme.write_text(
|
|
"\n".join(
|
|
[
|
|
f"K1 release {base}",
|
|
f"Built {datetime.now().strftime('%Y-%m-%d %H:%M:%S %z')}",
|
|
f"Git HEAD {commit}",
|
|
f"Git subject {subject}",
|
|
f"FW version {FW_VER} (settings UI Version[])",
|
|
f"Tone res toneRes_v{TONE_RES_VER} (no in-bin version; tag in filename)",
|
|
"",
|
|
"Contents:",
|
|
f" {mcu_name}",
|
|
f" size={mcu_dst.stat().st_size} sha256={sha256(mcu_dst)}",
|
|
f" flash with SoundWalkerIAP.exe (USB IAP)",
|
|
f" {res_name}",
|
|
f" size={res_dst.stat().st_size} sha256={sha256(res_dst)}",
|
|
" W25Q128 layout: logo@0 + Charg@0xCB70 + 1.bin@0x1B8F0 + 2.bin + 3.bin",
|
|
" Must pair with MCU v0.2.0 (address map changed).",
|
|
" SoundWalkerIAP.exe",
|
|
" 升级步骤.docx",
|
|
" FLASH_MAP.txt",
|
|
"",
|
|
"Notes:",
|
|
" - Boot logo uses UI0902 full-screen asset; packed logo.bin only pads 0x0..0xCB70.",
|
|
" - AutoBand legacy 0x9EB5F not remapped in this release.",
|
|
" - Flash MCU firmware AND tone .res together.",
|
|
"",
|
|
]
|
|
),
|
|
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()
|