feat: initial Zynq XC7Z100 Flasher GUI project
Add cross-platform GUI application for programming Zynq devices with step-by-step workflow: - CustomTkinter GUI with dark/light theme support - 4-step workflow: Check Env → Flash → Bootloader → Main Program - YAML configuration with relative path resolution - Serial monitoring and boot log parsing - TFTP upload with CRC32 verification - SSH/serial reboot management - Vivado/Impact tool auto-detection Project structure: - app.py: Entry point - src/: Core modules (config, flash, bitstream, tftp, serial, gui) - config/: Default configuration template - .flasher_env/: Python virtual environment
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
"""Reboot manager for Zynq Flasher GUI.
|
||||
|
||||
Handles rebooting the Zynq device via SSH or serial commands.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
import platform
|
||||
import shutil
|
||||
import subprocess
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from typing import Callable
|
||||
|
||||
from config_manager import Config
|
||||
|
||||
|
||||
@dataclass
|
||||
class RebootResult:
|
||||
"""Result of a reboot operation."""
|
||||
|
||||
success: bool
|
||||
message: str
|
||||
method: str = ""
|
||||
output: str = ""
|
||||
|
||||
|
||||
ProgressCallback = Callable[[str, str], None] | None
|
||||
|
||||
|
||||
def reboot_via_ssh(
|
||||
config: Config,
|
||||
timeout: int | None = None,
|
||||
callback: ProgressCallback = None,
|
||||
) -> RebootResult:
|
||||
"""Reboot the Zynq device via SSH.
|
||||
|
||||
Sends a 'reboot' command over SSH to the Zynq device.
|
||||
|
||||
Args:
|
||||
config: Application configuration.
|
||||
timeout: Seconds to wait for reboot acknowledgment.
|
||||
callback: Optional progress callback.
|
||||
|
||||
Returns:
|
||||
RebootResult with status.
|
||||
"""
|
||||
timeout = timeout or config.reboot_timeout
|
||||
ip = config.zynq_ip
|
||||
|
||||
if callback:
|
||||
callback("start", f"Sending reboot via SSH to {ip}...")
|
||||
|
||||
# Check ssh availability
|
||||
if not shutil.which("ssh"):
|
||||
return RebootResult(
|
||||
success=False,
|
||||
message="ssh command not found in PATH",
|
||||
method="ssh",
|
||||
)
|
||||
|
||||
# Try SSH reboot
|
||||
cmd = ["ssh", "-o", "ConnectTimeout=5", "-o", "StrictHostKeyChecking=no",
|
||||
f"root@{ip}", "reboot"]
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10,
|
||||
)
|
||||
if callback:
|
||||
if result.returncode == 0:
|
||||
callback("progress", f"Reboot command sent to {ip}")
|
||||
else:
|
||||
callback("error", f"SSH reboot failed: {result.stderr.strip()}")
|
||||
|
||||
# Wait for device to come back
|
||||
if result.returncode == 0 and callback:
|
||||
callback("progress", f"Waiting {timeout}s for device to reboot...")
|
||||
|
||||
return RebootResult(
|
||||
success=result.returncode == 0,
|
||||
message="Reboot command sent" if result.returncode == 0 else f"SSH reboot failed: {result.stderr.strip()}",
|
||||
method="ssh",
|
||||
output=result.stdout,
|
||||
)
|
||||
except subprocess.TimeoutExpired:
|
||||
return RebootResult(
|
||||
success=False,
|
||||
message="SSH reboot timed out",
|
||||
method="ssh",
|
||||
)
|
||||
except FileNotFoundError:
|
||||
return RebootResult(
|
||||
success=False,
|
||||
message="ssh command not found",
|
||||
method="ssh",
|
||||
)
|
||||
except OSError as e:
|
||||
return RebootResult(
|
||||
success=False,
|
||||
message=f"OS error: {e}",
|
||||
method="ssh",
|
||||
)
|
||||
|
||||
|
||||
def reboot_via_serial(
|
||||
config: Config,
|
||||
callback: ProgressCallback = None,
|
||||
) -> RebootResult:
|
||||
"""Reboot the Zynq device via serial port.
|
||||
|
||||
Sends a reboot command through the serial console.
|
||||
|
||||
Args:
|
||||
config: Application configuration.
|
||||
callback: Optional progress callback.
|
||||
|
||||
Returns:
|
||||
RebootResult with status.
|
||||
"""
|
||||
if not config.serial_port:
|
||||
return RebootResult(
|
||||
success=False,
|
||||
message="No serial port configured",
|
||||
method="serial",
|
||||
)
|
||||
|
||||
if callback:
|
||||
callback("start", f"Sending reboot via serial {config.serial_port}...")
|
||||
|
||||
try:
|
||||
import serial
|
||||
|
||||
with serial.Serial(
|
||||
config.serial_port,
|
||||
config.serial_baudrate,
|
||||
timeout=5,
|
||||
) as ser:
|
||||
ser.reset_input_buffer()
|
||||
# Send Ctrl+C to break out of any running process
|
||||
ser.write(b"\x03")
|
||||
time.sleep(0.5)
|
||||
# Send reboot command
|
||||
ser.write(b"reboot\r\n")
|
||||
ser.reset_input_buffer()
|
||||
|
||||
if callback:
|
||||
callback("progress", "Reboot command sent via serial")
|
||||
|
||||
return RebootResult(
|
||||
success=True,
|
||||
message="Reboot command sent via serial",
|
||||
method="serial",
|
||||
)
|
||||
except ImportError:
|
||||
return RebootResult(
|
||||
success=False,
|
||||
message="pyserial not installed",
|
||||
method="serial",
|
||||
)
|
||||
except serial.SerialException as e:
|
||||
return RebootResult(
|
||||
success=False,
|
||||
message=f"Serial error: {e}",
|
||||
method="serial",
|
||||
)
|
||||
except OSError as e:
|
||||
return RebootResult(
|
||||
success=False,
|
||||
message=f"OS error: {e}",
|
||||
method="serial",
|
||||
)
|
||||
|
||||
|
||||
def reboot_zynq(
|
||||
config: Config,
|
||||
method: str = "auto",
|
||||
timeout: int | None = None,
|
||||
callback: ProgressCallback = None,
|
||||
) -> RebootResult:
|
||||
"""Reboot the Zynq device using the best available method.
|
||||
|
||||
Tries SSH first, falls back to serial if SSH is unavailable.
|
||||
|
||||
Args:
|
||||
config: Application configuration.
|
||||
method: Reboot method: 'auto', 'ssh', or 'serial'.
|
||||
timeout: Seconds to wait for reboot.
|
||||
callback: Optional progress callback.
|
||||
|
||||
Returns:
|
||||
RebootResult with status.
|
||||
"""
|
||||
timeout = timeout or config.reboot_timeout
|
||||
|
||||
if method == "ssh":
|
||||
return reboot_via_ssh(config, timeout, callback)
|
||||
elif method == "serial":
|
||||
return reboot_via_serial(config, callback)
|
||||
|
||||
# Auto: try SSH first, then serial
|
||||
result = reboot_via_ssh(config, timeout, callback)
|
||||
if result.success:
|
||||
return result
|
||||
|
||||
if callback:
|
||||
callback("progress", "SSH failed, trying serial...")
|
||||
|
||||
return reboot_via_serial(config, callback)
|
||||
Reference in New Issue
Block a user