🐛 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:
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
bootloader_bin_path: /home/ly0kos/work/Verify/FW/tftp_app/bootloader/bootloader_z100_800M.bin
|
bootloader_bin_path: /home/ly0kos/work/Verify/FW/tftp_app/bootloader/bootloader_z100_800M.bin
|
||||||
bootloader_bit_path: /home/ly0kos/work/Verify/FW/tftp_app/bootloader/pl_arm_wrapper_hw_platform_0/pl_arm_wrapper.bit
|
bootloader_bit_path: /home/ly0kos/work/Verify/FW/tftp_app/bootloader/pl_arm_wrapper_hw_platform_0/pl_arm_wrapper.bit
|
||||||
bootloader_elf_path: /home/ly0kos/work/Verify/FW/tftp_app/bootloader/app.elf
|
bootloader_elf_path: /home/ly0kos/work/Verify/FW/tftp_app/bootloader/app.elf
|
||||||
erase_before_program: true
|
erase_all: true
|
||||||
firmware_bin_path: /home/ly0kos/work/Verify/FW/V1.3.1.3.3_06_01_pmu_cpld_check/V1.3.1.3.3_06_01_pmu_cpld_check.bin
|
firmware_bin_path: /home/ly0kos/work/Verify/FW/V1.3.1.3.3_06_01_pmu_cpld_check/V1.3.1.3.3_06_01_pmu_cpld_check.bin
|
||||||
flash_model: s25fl256s1
|
flash_model: s25fl256s1
|
||||||
flash_type: qspi-x4-single
|
flash_type: qspi-x4-single
|
||||||
|
|||||||
+60
-10
@@ -100,9 +100,10 @@ def wipe_flash(
|
|||||||
) -> FlashResult:
|
) -> FlashResult:
|
||||||
"""Erase QSPI flash before programming.
|
"""Erase QSPI flash before programming.
|
||||||
|
|
||||||
Uses config.erase_all to determine erase scope:
|
- erase_all=False: -erase_only (matches BIN size, works on all chips)
|
||||||
- True: -erase_all -erase_only (erase entire chip, may fail on some)
|
- erase_all=True: creates a temp file of full flash capacity, uses
|
||||||
- False: -erase_only (erase only sectors matching the BIN file, safer)
|
-erase_only with it. Avoids -erase_all flag which fails on
|
||||||
|
s25fl256s1 (no memory density info).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
config: Application configuration (needs fsbl_elf_path).
|
config: Application configuration (needs fsbl_elf_path).
|
||||||
@@ -130,14 +131,40 @@ def wipe_flash(
|
|||||||
if callback:
|
if callback:
|
||||||
callback("start", "Erasing QSPI flash...")
|
callback("start", "Erasing QSPI flash...")
|
||||||
|
|
||||||
cmd = _build_program_flash_cmd_base(flash_tool, config)
|
erase_all = getattr(config, 'erase_all', False)
|
||||||
if getattr(config, 'erase_all', False):
|
|
||||||
cmd += ["-erase_all", "-erase_only"] # Full chip erase
|
|
||||||
else:
|
|
||||||
cmd += ["-erase_only"] # Erase sectors matching BIN size
|
|
||||||
|
|
||||||
try:
|
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
|
success = result.returncode == 0
|
||||||
return FlashResult(
|
return FlashResult(
|
||||||
step="wipe",
|
step="wipe",
|
||||||
@@ -146,7 +173,30 @@ def wipe_flash(
|
|||||||
output=(result.stdout + result.stderr)[:4000],
|
output=(result.stdout + result.stderr)[:4000],
|
||||||
)
|
)
|
||||||
except subprocess.TimeoutExpired:
|
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(
|
def program_flash_bin(
|
||||||
|
|||||||
@@ -1055,9 +1055,12 @@ class MainWindow(ctk.CTk):
|
|||||||
elapsed = time.time() - t_step_start
|
elapsed = time.time() - t_step_start
|
||||||
self._log_message(f" ⏱ Step {i+1} completed in {elapsed:.0f}s")
|
self._log_message(f" ⏱ Step {i+1} completed in {elapsed:.0f}s")
|
||||||
|
|
||||||
# Step 2: mark all sub-steps done
|
# Step 2: mark sub-steps based on result
|
||||||
if i == 1 and hasattr(self, '_sub_step_frame'):
|
if i == 1 and hasattr(self, '_sub_step_frame'):
|
||||||
self._sub_step_frame.set_phase("done")
|
if success:
|
||||||
|
self._sub_step_frame.set_phase("done")
|
||||||
|
else:
|
||||||
|
self._sub_step_frame.set_phase("error")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
results.append(False)
|
results.append(False)
|
||||||
self._set_step_status(i, "error")
|
self._set_step_status(i, "error")
|
||||||
|
|||||||
+93
-63
@@ -29,6 +29,10 @@ from gui.styles import (
|
|||||||
ACCENT_DISABLED,
|
ACCENT_DISABLED,
|
||||||
CONNECTOR_COLOR,
|
CONNECTOR_COLOR,
|
||||||
DARK_FG,
|
DARK_FG,
|
||||||
|
CIRCLE_PENDING,
|
||||||
|
CIRCLE_RUNNING,
|
||||||
|
CIRCLE_COMPLETE,
|
||||||
|
CIRCLE_ERROR,
|
||||||
LIGHT_FG,
|
LIGHT_FG,
|
||||||
STEP_RUN_BG,
|
STEP_RUN_BG,
|
||||||
STEP_RUN_BG_HOVER,
|
STEP_RUN_BG_HOVER,
|
||||||
@@ -634,49 +638,81 @@ class SubStepFrame(ctk.CTkFrame):
|
|||||||
"""Collapsible frame showing flash operation sub-steps with status.
|
"""Collapsible frame showing flash operation sub-steps with status.
|
||||||
|
|
||||||
Three sub-steps: Erase → Program → Verify.
|
Three sub-steps: Erase → Program → Verify.
|
||||||
Each shows a status indicator (dot + label) updated via set_phase().
|
Status dots match main Step style (CIRCLE_* / ACCENT_* colors).
|
||||||
Collapsed by default, expands when a workflow step starts.
|
Collapsed by default, expands on workflow start.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, parent: ctk.CTkFrame) -> None:
|
def __init__(self, parent: ctk.CTkFrame) -> None:
|
||||||
"""Initialize the sub-step frame.
|
"""Initialize the sub-step frame."""
|
||||||
|
|
||||||
Args:
|
|
||||||
parent: Parent CTkFrame widget.
|
|
||||||
"""
|
|
||||||
super().__init__(parent, fg_color="transparent")
|
super().__init__(parent, fg_color="transparent")
|
||||||
self.grid_columnconfigure(1, weight=1)
|
self.grid_columnconfigure(0, weight=1)
|
||||||
|
|
||||||
# Collapse toggle
|
# Collapse toggle — row 0
|
||||||
self._collapsed = True
|
self._collapsed = True
|
||||||
|
toggle_frame = ctk.CTkFrame(self, fg_color="transparent")
|
||||||
|
toggle_frame.grid(row=0, column=0, sticky="ew", padx=(PADDING, PADDING), pady=(2, 0))
|
||||||
|
toggle_frame.grid_columnconfigure(1, weight=1)
|
||||||
|
|
||||||
self._toggle_btn = ctk.CTkButton(
|
self._toggle_btn = ctk.CTkButton(
|
||||||
self,
|
toggle_frame,
|
||||||
text="▶",
|
text="▶",
|
||||||
width=24,
|
width=20,
|
||||||
height=24,
|
height=20,
|
||||||
font=FONT_SMALL,
|
font=(FONT_SMALL[0], 9),
|
||||||
command=self._toggle,
|
command=self._toggle,
|
||||||
)
|
)
|
||||||
self._toggle_btn.grid(row=0, column=0, padx=(PADDING_SMALL, 4), pady=(2, 0), sticky="nw")
|
self._toggle_btn.grid(row=0, column=0, padx=(0, 4))
|
||||||
|
|
||||||
self._header_label = ctk.CTkLabel(
|
self._header_label = ctk.CTkLabel(
|
||||||
self,
|
toggle_frame,
|
||||||
text="Sub-steps",
|
text="Flash: Erase → Program → Verify",
|
||||||
font=FONT_SMALL,
|
font=FONT_BODY,
|
||||||
|
anchor="w",
|
||||||
)
|
)
|
||||||
self._header_label.grid(row=0, column=1, padx=(0, PADDING), pady=(2, 0), sticky="w")
|
self._header_label.grid(row=0, column=1, sticky="w")
|
||||||
|
|
||||||
# Sub-items (hidden initially)
|
# Sub-items (hidden initially)
|
||||||
self._sub_items: list[dict] = []
|
self._sub_items: list[dict] = []
|
||||||
sub_names = ["Erase", "Program", "Verify"]
|
sub_names = ["Erase ", "Program", "Verify "]
|
||||||
for i, name in enumerate(sub_names):
|
for i, name in enumerate(sub_names):
|
||||||
dot = ctk.CTkLabel(self, text="○", font=(FONT_SMALL[0], 10), text_color=CONNECTOR_COLOR)
|
item = self._build_sub_row(i + 1, name)
|
||||||
label = ctk.CTkLabel(self, text=name, font=FONT_SMALL, text_color=CONNECTOR_COLOR)
|
item["dot"].grid_remove()
|
||||||
dot.grid(row=i + 1, column=0, padx=(PADDING_SMALL + 14, 4), pady=(1, 1), sticky="e")
|
item["label"].grid_remove()
|
||||||
label.grid(row=i + 1, column=1, padx=(4, PADDING), pady=(1, 1), sticky="w")
|
item["pct"].grid_remove()
|
||||||
dot.grid_remove()
|
self._sub_items.append(item)
|
||||||
label.grid_remove()
|
|
||||||
self._sub_items.append({"dot": dot, "label": label, "name": name})
|
def _build_sub_row(self, row: int, name: str) -> dict:
|
||||||
|
"""Build one sub-item row with dot, label, and percentage label."""
|
||||||
|
dot = ctk.CTkLabel(
|
||||||
|
self, text="○",
|
||||||
|
width=28,
|
||||||
|
font=(FONT_BODY[0], FONT_BODY[1]),
|
||||||
|
text_color=CIRCLE_PENDING,
|
||||||
|
anchor="center",
|
||||||
|
)
|
||||||
|
dot.grid(row=row, column=0, padx=(PADDING + 20, 4), pady=(3, 3), sticky="w")
|
||||||
|
|
||||||
|
sub_frame = ctk.CTkFrame(self, fg_color="transparent")
|
||||||
|
sub_frame.grid(row=row, column=0, sticky="ew", padx=(PADDING + 52, PADDING), pady=(3, 3))
|
||||||
|
sub_frame.grid_columnconfigure(0, weight=1)
|
||||||
|
|
||||||
|
label = ctk.CTkLabel(
|
||||||
|
sub_frame, text=name,
|
||||||
|
font=FONT_BODY,
|
||||||
|
text_color=ACCENT_PENDING,
|
||||||
|
anchor="w",
|
||||||
|
)
|
||||||
|
label.grid(row=0, column=0, sticky="w")
|
||||||
|
|
||||||
|
pct = ctk.CTkLabel(
|
||||||
|
sub_frame, text="",
|
||||||
|
font=FONT_BODY,
|
||||||
|
text_color=ACCENT_PENDING,
|
||||||
|
anchor="e",
|
||||||
|
)
|
||||||
|
pct.grid(row=0, column=1, sticky="e", padx=(8, 0))
|
||||||
|
|
||||||
|
return {"dot": dot, "label": label, "pct": pct, "name": name}
|
||||||
|
|
||||||
def _toggle(self) -> None:
|
def _toggle(self) -> None:
|
||||||
"""Toggle collapse/expand."""
|
"""Toggle collapse/expand."""
|
||||||
@@ -685,73 +721,67 @@ class SubStepFrame(ctk.CTkFrame):
|
|||||||
for item in self._sub_items:
|
for item in self._sub_items:
|
||||||
if self._collapsed:
|
if self._collapsed:
|
||||||
item["dot"].grid_remove()
|
item["dot"].grid_remove()
|
||||||
item["label"].grid_remove()
|
item["label"].master.grid_remove()
|
||||||
else:
|
else:
|
||||||
item["dot"].grid()
|
item["dot"].grid()
|
||||||
item["label"].grid()
|
item["label"].master.grid()
|
||||||
|
|
||||||
def set_expanded(self, expanded: bool) -> None:
|
def set_expanded(self, expanded: bool) -> None:
|
||||||
"""Set expanded state.
|
"""Set expanded state."""
|
||||||
|
if expanded != self._collapsed:
|
||||||
Args:
|
|
||||||
expanded: True to expand, False to collapse.
|
|
||||||
"""
|
|
||||||
if expanded == self._collapsed:
|
|
||||||
self._toggle()
|
self._toggle()
|
||||||
|
|
||||||
def set_phase(self, phase: str, pct: int = -1) -> None:
|
def set_phase(self, phase: str, pct: int = -1) -> None:
|
||||||
"""Update a sub-step's status.
|
"""Update sub-step status.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
phase: One of 'erase', 'program', 'verify', 'done'.
|
phase: 'erase', 'program', 'verify', 'done', 'error'.
|
||||||
pct: Progress percentage, -1 to show "running", 100 for "done".
|
pct: Progress 0-100, -1 means running (no specific %).
|
||||||
"""
|
"""
|
||||||
phase_map = {"erase": 0, "program": 1, "verify": 2}
|
phase_map = {"erase": 0, "program": 1, "verify": 2}
|
||||||
colors = {
|
|
||||||
"pending": CONNECTOR_COLOR,
|
|
||||||
"running": PRIMARY_COLOR,
|
|
||||||
"complete": SUCCESS_COLOR,
|
|
||||||
"error": DANGER_COLOR,
|
|
||||||
}
|
|
||||||
|
|
||||||
if phase == "done":
|
if phase == "done":
|
||||||
# Mark all as complete
|
|
||||||
for item in self._sub_items:
|
for item in self._sub_items:
|
||||||
item["dot"].configure(text="●", text_color=SUCCESS_COLOR)
|
item["dot"].configure(text="●", text_color=CIRCLE_COMPLETE)
|
||||||
item["label"].configure(text_color=SUCCESS_COLOR)
|
item["label"].configure(text_color=SUCCESS_COLOR)
|
||||||
|
item["pct"].configure(text="OK", text_color=SUCCESS_COLOR)
|
||||||
|
return
|
||||||
|
|
||||||
|
if phase == "error":
|
||||||
|
for item in self._sub_items:
|
||||||
|
item["dot"].configure(text="✗", text_color=CIRCLE_ERROR)
|
||||||
|
item["label"].configure(text_color=DANGER_COLOR)
|
||||||
|
item["pct"].configure(text="", text_color=DANGER_COLOR)
|
||||||
return
|
return
|
||||||
|
|
||||||
idx = phase_map.get(phase, -1)
|
idx = phase_map.get(phase, -1)
|
||||||
if idx < 0:
|
if idx < 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Determine state
|
# Running with progress
|
||||||
if pct >= 100:
|
pct_text = f"{pct}%" if pct >= 0 else ""
|
||||||
state = "complete"
|
running_text = f" {pct_text}" if pct_text else ""
|
||||||
dot_text = "●"
|
|
||||||
elif pct >= 0:
|
|
||||||
state = "running"
|
|
||||||
dot_text = "◉"
|
|
||||||
else:
|
|
||||||
state = "running"
|
|
||||||
dot_text = "◉"
|
|
||||||
|
|
||||||
# Update current and previous
|
|
||||||
for i, item in enumerate(self._sub_items):
|
for i, item in enumerate(self._sub_items):
|
||||||
if i < idx:
|
if i < idx:
|
||||||
item["dot"].configure(text="●", text_color=SUCCESS_COLOR)
|
item["dot"].configure(text="●", text_color=CIRCLE_COMPLETE)
|
||||||
item["label"].configure(text_color=SUCCESS_COLOR)
|
item["label"].configure(text_color=SUCCESS_COLOR)
|
||||||
|
item["pct"].configure(text="OK", text_color=SUCCESS_COLOR)
|
||||||
elif i == idx:
|
elif i == idx:
|
||||||
item["dot"].configure(text=dot_text, text_color=colors[state])
|
item["dot"].configure(text="◉", text_color=CIRCLE_RUNNING)
|
||||||
item["label"].configure(text_color=colors[state])
|
item["label"].configure(text_color=ACCENT_RUNNING)
|
||||||
|
item["pct"].configure(text=pct_text, text_color=ACCENT_RUNNING)
|
||||||
else:
|
else:
|
||||||
item["dot"].configure(text="○", text_color=colors["pending"])
|
item["dot"].configure(text="○", text_color=CIRCLE_PENDING)
|
||||||
item["label"].configure(text_color=colors["pending"])
|
item["label"].configure(text_color=ACCENT_PENDING)
|
||||||
|
item["pct"].configure(text="", text_color=ACCENT_PENDING)
|
||||||
|
|
||||||
def reset(self) -> None:
|
def reset(self) -> None:
|
||||||
"""Reset all sub-steps to pending state."""
|
"""Reset all sub-steps to pending state."""
|
||||||
for item in self._sub_items:
|
for item in self._sub_items:
|
||||||
item["dot"].configure(text="○", text_color=CONNECTOR_COLOR)
|
item["dot"].configure(text="○", text_color=CIRCLE_PENDING)
|
||||||
item["label"].configure(text_color=CONNECTOR_COLOR)
|
item["label"].configure(text_color=ACCENT_PENDING)
|
||||||
|
item["pct"].configure(text="", text_color=ACCENT_PENDING)
|
||||||
if not self._collapsed:
|
if not self._collapsed:
|
||||||
self._toggle()
|
self._toggle()
|
||||||
|
self._toggle()
|
||||||
|
|||||||
Reference in New Issue
Block a user