🎨 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_monitor: UartMonitor | None = None
|
||||||
self._uart_window: UartMonitorWindow | None = None
|
self._uart_window: UartMonitorWindow | None = None
|
||||||
self._flash_phase: str = ""
|
self._flash_phase: str = ""
|
||||||
|
self._tftp_phase: str = ""
|
||||||
|
|
||||||
# StringVar references for config sync
|
# StringVar references for config sync
|
||||||
self._ip_string_var: ctk.StringVar | None = None
|
self._ip_string_var: ctk.StringVar | None = None
|
||||||
@@ -276,7 +277,7 @@ class MainWindow(ctk.CTk):
|
|||||||
# ── Left Panel: Workflow Steps ──
|
# ── Left Panel: Workflow Steps ──
|
||||||
left_frame = ctk.CTkFrame(main_frame)
|
left_frame = ctk.CTkFrame(main_frame)
|
||||||
left_frame.grid(row=1, column=0, sticky="nsew", padx=(0, PADDING))
|
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)
|
left_frame.grid_columnconfigure(0, weight=1)
|
||||||
|
|
||||||
self._build_workflow_panel(left_frame)
|
self._build_workflow_panel(left_frame)
|
||||||
@@ -285,10 +286,12 @@ class MainWindow(ctk.CTk):
|
|||||||
# ── Right Panel: Configuration ──
|
# ── Right Panel: Configuration ──
|
||||||
right_frame = ctk.CTkFrame(main_frame)
|
right_frame = ctk.CTkFrame(main_frame)
|
||||||
right_frame.grid(row=1, column=1, sticky="nsew", padx=(PADDING, 0))
|
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_config_panel(right_frame)
|
||||||
self._build_serial_panel(right_frame)
|
self._build_serial_panel(right_frame)
|
||||||
|
self._build_utility_panel(right_frame)
|
||||||
self._build_status_panel(right_frame)
|
self._build_status_panel(right_frame)
|
||||||
|
|
||||||
def _build_header(self, parent: ctk.CTkFrame) -> None:
|
def _build_header(self, parent: ctk.CTkFrame) -> None:
|
||||||
@@ -601,7 +604,6 @@ class MainWindow(ctk.CTk):
|
|||||||
row=1, column=0, sticky="nsew",
|
row=1, column=0, sticky="nsew",
|
||||||
padx=PADDING, pady=PADDING,
|
padx=PADDING, pady=PADDING,
|
||||||
)
|
)
|
||||||
panel.grid_rowconfigure(3, weight=1)
|
|
||||||
panel.grid_columnconfigure(0, weight=1)
|
panel.grid_columnconfigure(0, weight=1)
|
||||||
|
|
||||||
title = ctk.CTkLabel(
|
title = ctk.CTkLabel(
|
||||||
@@ -644,20 +646,65 @@ class MainWindow(ctk.CTk):
|
|||||||
)
|
)
|
||||||
uart_btn.grid(row=2, column=0, padx=PADDING, pady=PADDING_SMALL)
|
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(
|
self._test_version_btn = ctk.CTkButton(
|
||||||
panel,
|
panel,
|
||||||
text="Test Version",
|
text="Version (Serial)",
|
||||||
font=FONT_BODY,
|
font=FONT_BODY,
|
||||||
command=self._test_serial_version,
|
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:
|
def _build_status_panel(self, parent: ctk.CTkFrame) -> None:
|
||||||
"""Build the status display panel."""
|
"""Build the status display panel."""
|
||||||
panel = ctk.CTkFrame(parent, corner_radius=CORNER_RADIUS)
|
panel = ctk.CTkFrame(parent, corner_radius=CORNER_RADIUS)
|
||||||
panel.grid(
|
panel.grid(
|
||||||
row=2, column=0, sticky="nsew",
|
row=3, column=0, sticky="nsew",
|
||||||
padx=PADDING, pady=PADDING,
|
padx=PADDING, pady=PADDING,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -930,6 +977,7 @@ class MainWindow(ctk.CTk):
|
|||||||
|
|
||||||
# 4a: TFTP Upload
|
# 4a: TFTP Upload
|
||||||
self._log_message("Step 4a: TFTP upload...")
|
self._log_message("Step 4a: TFTP upload...")
|
||||||
|
self._tftp_phase = "Upload"
|
||||||
if hasattr(self, '_tftp_sub_step_frame'):
|
if hasattr(self, '_tftp_sub_step_frame'):
|
||||||
self._tftp_sub_step_frame.set_phase("Upload")
|
self._tftp_sub_step_frame.set_phase("Upload")
|
||||||
try:
|
try:
|
||||||
@@ -952,14 +1000,20 @@ class MainWindow(ctk.CTk):
|
|||||||
self._log_message(" Skipping download verify (TFTP upload failed)")
|
self._log_message(" Skipping download verify (TFTP upload failed)")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# Delay to let Zynq TFTP server stabilize
|
||||||
|
self._log_message(" ⏳ Waiting 2s before download...")
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
# 4b: TFTP Download Verify
|
# 4b: TFTP Download Verify
|
||||||
self._log_message("Step 4b: TFTP download verify...")
|
self._log_message("Step 4b: TFTP download verify...")
|
||||||
|
self._tftp_phase = "Download Verify"
|
||||||
if hasattr(self, '_tftp_sub_step_frame'):
|
if hasattr(self, '_tftp_sub_step_frame'):
|
||||||
self._tftp_sub_step_frame.set_phase("Download Verify")
|
self._tftp_sub_step_frame.set_phase("Download Verify")
|
||||||
try:
|
try:
|
||||||
download_result = tftp_download(
|
download_result = tftp_download(
|
||||||
self._config, remote_name,
|
self._config, remote_name,
|
||||||
callback=self._tftp_sub_callback,
|
callback=self._tftp_sub_callback,
|
||||||
|
expected_size=bin_path.stat().st_size,
|
||||||
)
|
)
|
||||||
status = "✓" if download_result.success else "✗"
|
status = "✓" if download_result.success else "✗"
|
||||||
self._log_message(f" {status} {download_result.message}")
|
self._log_message(f" {status} {download_result.message}")
|
||||||
@@ -1007,7 +1061,7 @@ class MainWindow(ctk.CTk):
|
|||||||
try:
|
try:
|
||||||
reboot_result = reboot_zynq(
|
reboot_result = reboot_zynq(
|
||||||
self._config,
|
self._config,
|
||||||
method="auto",
|
method="jtag",
|
||||||
callback=self._reboot_callback,
|
callback=self._reboot_callback,
|
||||||
)
|
)
|
||||||
status = "✓" if reboot_result.success else "✗"
|
status = "✓" if reboot_result.success else "✗"
|
||||||
@@ -1058,7 +1112,15 @@ class MainWindow(ctk.CTk):
|
|||||||
self._log_message(f" [{status}] {message}")
|
self._log_message(f" [{status}] {message}")
|
||||||
|
|
||||||
def _tftp_sub_callback(self, status: str, message: str) -> None:
|
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}")
|
self._log_message(f" [{status}] {message}")
|
||||||
|
|
||||||
def _reboot_callback(self, status: str, message: str) -> None:
|
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")
|
self._status.set_status(f"Error: {e}", "error")
|
||||||
finally:
|
finally:
|
||||||
# Re-enable button
|
# 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()
|
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 ────────────────────────────────────────────
|
# ── Vitis Check ────────────────────────────────────────────
|
||||||
|
|
||||||
def _check_vitis(self) -> None:
|
def _check_vitis(self) -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user