🐛 fix: sub-step readability, erase_all workaround, error state

Fix 1 — SubStepFrame readability:
- FONT_BODY (12pt) instead of FONT_SMALL dots/labels
- Number-circle colored dots (CIRCLE_PENDING/RUNNING/COMPLETE/ERROR)
- Accent-colored labels matching StepHeader (ACCENT_*)
- Percentage label next to running phase name

Fix 2 — erase_all full-chip erase:
- Creates temp zero-filled file of flash capacity (from flash_model)
- Uses -erase_only with dummy file instead of failing -erase_all flag
- Supports s25fl256s1 (32MB) which lacks density info for -erase_all
- _flash_capacity() maps model name→bytes

Fix 3 — Error state:
- set_phase('error') shows ✗ red on all sub-steps
- Only shows 'done' (green ●) on actual success

Fix 4 — Style consistency:
- Sub-step colors match StepHeader: CIRCLE_*/ACCENT_*/SUCCESS/DANGER
This commit is contained in:
Jeremy Shen
2026-06-10 13:51:41 +08:00
parent 95c04d94ae
commit e3e3763dc4
4 changed files with 159 additions and 76 deletions
+60 -10
View File
@@ -100,9 +100,10 @@ def wipe_flash(
) -> FlashResult:
"""Erase QSPI flash before programming.
Uses config.erase_all to determine erase scope:
- True: -erase_all -erase_only (erase entire chip, may fail on some)
- False: -erase_only (erase only sectors matching the BIN file, safer)
- erase_all=False: -erase_only (matches BIN size, works on all chips)
- erase_all=True: creates a temp file of full flash capacity, uses
-erase_only with it. Avoids -erase_all flag which fails on
s25fl256s1 (no memory density info).
Args:
config: Application configuration (needs fsbl_elf_path).
@@ -130,14 +131,40 @@ def wipe_flash(
if callback:
callback("start", "Erasing QSPI flash...")
cmd = _build_program_flash_cmd_base(flash_tool, config)
if getattr(config, 'erase_all', False):
cmd += ["-erase_all", "-erase_only"] # Full chip erase
else:
cmd += ["-erase_only"] # Erase sectors matching BIN size
erase_all = getattr(config, 'erase_all', False)
try:
result = _run_command(cmd, timeout=config.step_timeouts.get("flash_erase", 120), callback=callback)
if erase_all:
flash_size = _flash_capacity(config)
import tempfile, os
tmp = tempfile.NamedTemporaryFile(delete=False, suffix='.bin')
try:
tmp.seek(flash_size - 1)
tmp.write(b'\x00')
tmp.close()
cmd = [
flash_tool,
"-f", tmp.name,
"-fsbl", str(fsbl_path),
"-flash_type", config.flash_type,
"-erase_only",
]
result = _run_command(
cmd,
timeout=config.step_timeouts.get("flash_erase", 120),
callback=callback,
)
finally:
os.unlink(tmp.name)
else:
cmd = _build_program_flash_cmd_base(flash_tool, config)
cmd += ["-erase_only"]
result = _run_command(
cmd,
timeout=config.step_timeouts.get("flash_erase", 120),
callback=callback,
)
success = result.returncode == 0
return FlashResult(
step="wipe",
@@ -146,7 +173,30 @@ def wipe_flash(
output=(result.stdout + result.stderr)[:4000],
)
except subprocess.TimeoutExpired:
return FlashResult(step="wipe", success=False, message="Flash erase timed out (300s)")
return FlashResult(step="wipe", success=False, message="Flash erase timed out")
except FileNotFoundError:
return FlashResult(step="wipe", success=False, message="program_flash not found")
def _flash_capacity(config: Config) -> int:
"""Get flash chip capacity in bytes from flash_model.
Args:
config: Application configuration.
Returns:
Capacity in bytes (default 32 MiB).
"""
model = getattr(config, 'flash_model', 's25fl256s1').lower()
capacities: dict[str, int] = {
's25fl256': 32 * 1024 * 1024,
's25fl128': 16 * 1024 * 1024,
's25fl064': 8 * 1024 * 1024,
}
for key, cap in capacities.items():
if key in model:
return cap
return 32 * 1024 * 1024
def program_flash_bin(