feat: flash phase tracking, erase checkbox, flash_model config

Step 2 UI now shows real-time flash operation phases parsed from program_flash output:
- Erase: "Performing Erase Operation..." → sf erase → Erased: OK
- Program: "Performing Program Operation..." → write blocks 0%→100%
- Verify: "Performing Verify Operation..." → read+cmp 0%→100%

Configuration additions:
- erase_before_program checkbox: controls whether to pre-erase before programming
- flash_model: chip type for capacity reference (s25fl256s1 = 32 MiB)

subprocess_utils.py now emits 'phase' callbacks (erase/program/verify/done)
detected from program_flash output transitions.
This commit is contained in:
Jeremy Shen
2026-06-10 13:39:11 +08:00
parent 2c48224aea
commit ea3e3b8ac7
6 changed files with 101 additions and 17 deletions
+27 -2
View File
@@ -1,7 +1,8 @@
"""Non-blocking subprocess execution with real-time output parsing.
Provides run_streaming() which streams stdout/stderr to callbacks
in real time, parsing for warnings, errors, and progress indicators.
in real time, parsing for warnings, errors, progress indicators,
and flash operation phase transitions (erase/program/verify).
Used by flash_programmer and bitstream_programmer.
"""
@@ -15,7 +16,7 @@ from typing import Callable
StreamCallback = Callable[[str, str], None] | None
"""Callback type: (category, message) where category is one of:
'out', 'err', 'warn', 'error', 'progress', 'done'"""
'out', 'err', 'warn', 'error', 'progress', 'phase', 'done'"""
# Regex patterns for parsing tool output
_WARNING_RE = re.compile(r"WARNING\s*[:\[].*", re.IGNORECASE)
@@ -24,6 +25,12 @@ _PROGRESS_RE = re.compile(r"(\d{1,3})\s*%")
_INFO_KEYWORDS = ["connected", "downloading", "running", "initialization",
"verif", "write", "erase", "flash operation"]
# Phase transition patterns for program_flash output
_PHASE_ERASE_RE = re.compile(r"Performing Erase Operation", re.IGNORECASE)
_PHASE_PROGRAM_RE = re.compile(r"Performing Program Operation", re.IGNORECASE)
_PHASE_VERIFY_RE = re.compile(r"Performing Verify Operation", re.IGNORECASE)
_PHASE_DONE_RE = re.compile(r"(Erase|Program|Verify) Operation successful", re.IGNORECASE)
def run_streaming(
cmd: list[str],
@@ -133,6 +140,24 @@ def _parse_and_callback(
callback("warn", line)
return
# Check for phase transitions (program_flash output)
if _PHASE_ERASE_RE.search(line):
callback("phase", "erase")
callback("out", line)
return
if _PHASE_PROGRAM_RE.search(line):
callback("phase", "program")
callback("out", line)
return
if _PHASE_VERIFY_RE.search(line):
callback("phase", "verify")
callback("out", line)
return
if _PHASE_DONE_RE.search(line):
callback("phase", "done")
callback("out", line)
return
# Check for progress percentage
m = _PROGRESS_RE.search(line)
if m: