🎨 style: compact 2-col Utility panel, both reboot buttons red
- Reboot (UDP) now uses DANGER_COLOR like Reboot (JTAG) - 2×2 grid layout: reboot row + version row - Buttons stretch with sticky=ew, equal column weights - Title spans both columns
This commit is contained in:
+173
-10
@@ -85,6 +85,7 @@ class MainWindow(ctk.CTk):
|
||||
self._uart_monitor: UartMonitor | None = None
|
||||
self._uart_window: UartMonitorWindow | None = None
|
||||
self._flash_phase: str = ""
|
||||
self._tftp_phase: str = ""
|
||||
|
||||
# StringVar references for config sync
|
||||
self._ip_string_var: ctk.StringVar | None = None
|
||||
@@ -276,7 +277,7 @@ class MainWindow(ctk.CTk):
|
||||
# ── Left Panel: Workflow Steps ──
|
||||
left_frame = ctk.CTkFrame(main_frame)
|
||||
left_frame.grid(row=1, column=0, sticky="nsew", padx=(0, PADDING))
|
||||
left_frame.grid_rowconfigure(3, weight=1)
|
||||
left_frame.grid_rowconfigure(1, weight=1)
|
||||
left_frame.grid_columnconfigure(0, weight=1)
|
||||
|
||||
self._build_workflow_panel(left_frame)
|
||||
@@ -285,10 +286,12 @@ class MainWindow(ctk.CTk):
|
||||
# ── Right Panel: Configuration ──
|
||||
right_frame = ctk.CTkFrame(main_frame)
|
||||
right_frame.grid(row=1, column=1, sticky="nsew", padx=(PADDING, 0))
|
||||
right_frame.grid_rowconfigure(4, weight=1)
|
||||
right_frame.grid_rowconfigure(3, weight=1)
|
||||
right_frame.grid_columnconfigure(0, weight=1)
|
||||
|
||||
self._build_config_panel(right_frame)
|
||||
self._build_serial_panel(right_frame)
|
||||
self._build_utility_panel(right_frame)
|
||||
self._build_status_panel(right_frame)
|
||||
|
||||
def _build_header(self, parent: ctk.CTkFrame) -> None:
|
||||
@@ -601,7 +604,6 @@ class MainWindow(ctk.CTk):
|
||||
row=1, column=0, sticky="nsew",
|
||||
padx=PADDING, pady=PADDING,
|
||||
)
|
||||
panel.grid_rowconfigure(3, weight=1)
|
||||
panel.grid_columnconfigure(0, weight=1)
|
||||
|
||||
title = ctk.CTkLabel(
|
||||
@@ -644,20 +646,65 @@ class MainWindow(ctk.CTk):
|
||||
)
|
||||
uart_btn.grid(row=2, column=0, padx=PADDING, pady=PADDING_SMALL)
|
||||
|
||||
# Test version button
|
||||
def _build_utility_panel(self, parent: ctk.CTkFrame) -> None:
|
||||
"""Build the utility tools panel."""
|
||||
panel = ctk.CTkFrame(parent, corner_radius=CORNER_RADIUS)
|
||||
panel.grid(
|
||||
row=2, column=0, sticky="nsew",
|
||||
padx=PADDING, pady=PADDING,
|
||||
)
|
||||
panel.grid_columnconfigure((0, 1), weight=1)
|
||||
|
||||
title = ctk.CTkLabel(
|
||||
panel,
|
||||
text="Utilities",
|
||||
font=FONT_HEADING,
|
||||
)
|
||||
title.grid(row=0, column=0, columnspan=2, padx=PADDING, pady=PADDING)
|
||||
|
||||
# Row 1: Reboot buttons (both red)
|
||||
self._jtag_reboot_btn = ctk.CTkButton(
|
||||
panel,
|
||||
text="Reboot (JTAG)",
|
||||
font=FONT_BODY,
|
||||
fg_color=DANGER_COLOR,
|
||||
hover_color="#B92B2F",
|
||||
command=self._jtag_reboot,
|
||||
)
|
||||
self._jtag_reboot_btn.grid(row=1, column=0, padx=PADDING_SMALL, pady=PADDING_SMALL, sticky="ew")
|
||||
|
||||
self._udp_reboot_btn = ctk.CTkButton(
|
||||
panel,
|
||||
text="Reboot (UDP)",
|
||||
font=FONT_BODY,
|
||||
fg_color=DANGER_COLOR,
|
||||
hover_color="#B92B2F",
|
||||
command=self._udp_reboot,
|
||||
)
|
||||
self._udp_reboot_btn.grid(row=1, column=1, padx=PADDING_SMALL, pady=PADDING_SMALL, sticky="ew")
|
||||
|
||||
# Row 2: Version buttons
|
||||
self._udp_version_btn = ctk.CTkButton(
|
||||
panel,
|
||||
text="Version (UDP)",
|
||||
font=FONT_BODY,
|
||||
command=self._udp_version,
|
||||
)
|
||||
self._udp_version_btn.grid(row=2, column=0, padx=PADDING_SMALL, pady=PADDING_SMALL, sticky="ew")
|
||||
|
||||
self._test_version_btn = ctk.CTkButton(
|
||||
panel,
|
||||
text="Test Version",
|
||||
text="Version (Serial)",
|
||||
font=FONT_BODY,
|
||||
command=self._test_serial_version,
|
||||
)
|
||||
self._test_version_btn.grid(row=3, column=0, padx=PADDING, pady=PADDING_SMALL)
|
||||
self._test_version_btn.grid(row=2, column=1, padx=PADDING_SMALL, pady=PADDING_SMALL, sticky="ew")
|
||||
|
||||
def _build_status_panel(self, parent: ctk.CTkFrame) -> None:
|
||||
"""Build the status display panel."""
|
||||
panel = ctk.CTkFrame(parent, corner_radius=CORNER_RADIUS)
|
||||
panel.grid(
|
||||
row=2, column=0, sticky="nsew",
|
||||
row=3, column=0, sticky="nsew",
|
||||
padx=PADDING, pady=PADDING,
|
||||
)
|
||||
|
||||
@@ -930,6 +977,7 @@ class MainWindow(ctk.CTk):
|
||||
|
||||
# 4a: TFTP Upload
|
||||
self._log_message("Step 4a: TFTP upload...")
|
||||
self._tftp_phase = "Upload"
|
||||
if hasattr(self, '_tftp_sub_step_frame'):
|
||||
self._tftp_sub_step_frame.set_phase("Upload")
|
||||
try:
|
||||
@@ -952,14 +1000,20 @@ class MainWindow(ctk.CTk):
|
||||
self._log_message(" Skipping download verify (TFTP upload failed)")
|
||||
return False
|
||||
|
||||
# Delay to let Zynq TFTP server stabilize
|
||||
self._log_message(" ⏳ Waiting 2s before download...")
|
||||
time.sleep(2)
|
||||
|
||||
# 4b: TFTP Download Verify
|
||||
self._log_message("Step 4b: TFTP download verify...")
|
||||
self._tftp_phase = "Download Verify"
|
||||
if hasattr(self, '_tftp_sub_step_frame'):
|
||||
self._tftp_sub_step_frame.set_phase("Download Verify")
|
||||
try:
|
||||
download_result = tftp_download(
|
||||
self._config, remote_name,
|
||||
callback=self._tftp_sub_callback,
|
||||
expected_size=bin_path.stat().st_size,
|
||||
)
|
||||
status = "✓" if download_result.success else "✗"
|
||||
self._log_message(f" {status} {download_result.message}")
|
||||
@@ -1007,7 +1061,7 @@ class MainWindow(ctk.CTk):
|
||||
try:
|
||||
reboot_result = reboot_zynq(
|
||||
self._config,
|
||||
method="auto",
|
||||
method="jtag",
|
||||
callback=self._reboot_callback,
|
||||
)
|
||||
status = "✓" if reboot_result.success else "✗"
|
||||
@@ -1058,7 +1112,15 @@ class MainWindow(ctk.CTk):
|
||||
self._log_message(f" [{status}] {message}")
|
||||
|
||||
def _tftp_sub_callback(self, status: str, message: str) -> None:
|
||||
"""Callback for TFTP sub-step progress."""
|
||||
"""Callback for TFTP sub-step progress — drives TftpSubStepFrame."""
|
||||
if status == "progress":
|
||||
try:
|
||||
pct = int(message.rstrip('%'))
|
||||
except ValueError:
|
||||
pct = -1
|
||||
if hasattr(self, '_tftp_sub_step_frame'):
|
||||
self._tftp_sub_step_frame.set_phase(self._tftp_phase, pct)
|
||||
return
|
||||
self._log_message(f" [{status}] {message}")
|
||||
|
||||
def _reboot_callback(self, status: str, message: str) -> None:
|
||||
@@ -1402,10 +1464,111 @@ class MainWindow(ctk.CTk):
|
||||
self._status.set_status(f"Error: {e}", "error")
|
||||
finally:
|
||||
# Re-enable button
|
||||
self.after(0, lambda: self._test_version_btn.configure(state="normal", text="Test Version"))
|
||||
self.after(0, lambda: self._test_version_btn.configure(state="normal", text="Version (Serial)"))
|
||||
|
||||
threading.Thread(target=test_version_thread, daemon=True).start()
|
||||
|
||||
def _jtag_reboot(self) -> None:
|
||||
"""Reboot the Zynq board via JTAG system reset."""
|
||||
if not self._config:
|
||||
self._log_message("No config loaded — cannot reboot")
|
||||
return
|
||||
|
||||
self._log_message(f"JTAG reboot: sending system reset to {self._config.zynq_ip}...")
|
||||
self._jtag_reboot_btn.configure(state="disabled", text="Rebooting...")
|
||||
|
||||
def _reboot_thread():
|
||||
try:
|
||||
from reboot_manager import reboot_via_jtag
|
||||
|
||||
result = reboot_via_jtag(
|
||||
self._config,
|
||||
callback=lambda s, m: self._log_message(f" [{s}] {m}"),
|
||||
)
|
||||
if result.success:
|
||||
self._log_message(f" ✓ JTAG reset: {result.message}")
|
||||
self._status.set_status("JTAG reset complete", "success")
|
||||
else:
|
||||
self._log_message(f" ✗ JTAG reset: {result.message}")
|
||||
self._status.set_status(f"JTAG reset failed: {result.message}", "error")
|
||||
if result.output:
|
||||
self._log_message(f" Output: {result.output[:500]}")
|
||||
except Exception as e:
|
||||
self._log_message(f" ✗ JTAG reboot error: {e}")
|
||||
self._status.set_status(f"Error: {e}", "error")
|
||||
finally:
|
||||
self.after(0, lambda: self._jtag_reboot_btn.configure(
|
||||
state="normal", text="Reboot (JTAG)",
|
||||
))
|
||||
|
||||
threading.Thread(target=_reboot_thread, daemon=True).start()
|
||||
|
||||
def _udp_reboot(self) -> None:
|
||||
"""Reboot the Zynq board via UDP command."""
|
||||
if not self._config:
|
||||
self._log_message("No config loaded — cannot reboot")
|
||||
return
|
||||
|
||||
self._log_message(f"UDP reboot: sending reboot() to {self._config.zynq_ip}...")
|
||||
self._udp_reboot_btn.configure(state="disabled", text="Rebooting...")
|
||||
|
||||
def _thread():
|
||||
try:
|
||||
from reboot_manager import reboot_via_udp
|
||||
|
||||
result = reboot_via_udp(
|
||||
self._config,
|
||||
callback=lambda s, m: self._log_message(f" [{s}] {m}"),
|
||||
)
|
||||
if result.success:
|
||||
self._log_message(f" ✓ UDP reboot: {result.message}")
|
||||
self._status.set_status("UDP reboot complete", "success")
|
||||
else:
|
||||
self._log_message(f" ✗ UDP reboot: {result.message}")
|
||||
self._status.set_status(f"UDP reboot failed", "error")
|
||||
except Exception as e:
|
||||
self._log_message(f" ✗ UDP reboot error: {e}")
|
||||
self._status.set_status(f"Error: {e}", "error")
|
||||
finally:
|
||||
self.after(0, lambda: self._udp_reboot_btn.configure(
|
||||
state="normal", text="Reboot (UDP)",
|
||||
))
|
||||
|
||||
threading.Thread(target=_thread, daemon=True).start()
|
||||
|
||||
def _udp_version(self) -> None:
|
||||
"""Query Zynq firmware version via UDP."""
|
||||
if not self._config:
|
||||
self._log_message("No config loaded")
|
||||
return
|
||||
|
||||
self._log_message(f"UDP version: sending ver() to {self._config.zynq_ip}...")
|
||||
self._udp_version_btn.configure(state="disabled", text="Querying...")
|
||||
|
||||
def _thread():
|
||||
try:
|
||||
from reboot_manager import get_version_via_udp
|
||||
|
||||
result = get_version_via_udp(
|
||||
self._config,
|
||||
callback=lambda s, m: self._log_message(f" [{s}] {m}"),
|
||||
)
|
||||
if result.success:
|
||||
self._log_message(f" ✓ Version: {result.version}")
|
||||
self._status.set_status(f"Version: {result.version}", "success")
|
||||
else:
|
||||
self._log_message(f" ✗ {result.message}")
|
||||
self._status.set_status(f"Query failed", "error")
|
||||
except Exception as e:
|
||||
self._log_message(f" ✗ UDP version error: {e}")
|
||||
self._status.set_status(f"Error: {e}", "error")
|
||||
finally:
|
||||
self.after(0, lambda: self._udp_version_btn.configure(
|
||||
state="normal", text="Version (UDP)",
|
||||
))
|
||||
|
||||
threading.Thread(target=_thread, daemon=True).start()
|
||||
|
||||
# ── Vitis Check ────────────────────────────────────────────
|
||||
|
||||
def _check_vitis(self) -> None:
|
||||
|
||||
Reference in New Issue
Block a user