✨ feat: UART boot wait, serial validation, xsct logging, layout fix
Config: - Add boot_wait_delay (default 10s) for post-reboot wait time Layout: - Restore row weights for proportional scaling when maximized - CTkScrollableFrame handles overflow when window is small Step 3: - Log xsct/vivado tool output (last 20 lines per result) for detailed diagnostics Step 4: - UART boot wait: monitor for IP/version via UART after reboot - If UART unavailable, fall back to config.boot_wait_delay - Reduces unnecessary wait time when boot is detected early Serial: - Real-time port validation on selection change (CTkComboBox command) - Persistent UART unavailable warning until valid port selected - Auto-restart UART monitor on port switch - Graceful cross-platform handling
This commit is contained in:
+44
-22
@@ -9,7 +9,6 @@ from __future__ import annotations
|
||||
import binascii
|
||||
import socket
|
||||
import struct
|
||||
import tempfile
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Callable
|
||||
@@ -55,7 +54,8 @@ def _compute_crc32(file_path: str | Path) -> int:
|
||||
|
||||
|
||||
def _tftp_put(host: str, file_path: str, remote_name: str,
|
||||
timeout: float = 10.0) -> tuple[bool, str]:
|
||||
timeout: float = 10.0,
|
||||
callback: Callable[[str, str], None] | None = None) -> tuple[bool, str]:
|
||||
"""Upload a file to a TFTP server using pure Python.
|
||||
|
||||
Args:
|
||||
@@ -63,6 +63,7 @@ def _tftp_put(host: str, file_path: str, remote_name: str,
|
||||
file_path: Local file path.
|
||||
remote_name: Remote filename.
|
||||
timeout: Socket timeout in seconds.
|
||||
callback: Optional progress callback (status, message).
|
||||
|
||||
Returns:
|
||||
(success, message).
|
||||
@@ -112,6 +113,8 @@ def _tftp_put(host: str, file_path: str, remote_name: str,
|
||||
pct = offset * 100 // total
|
||||
print(f"[TFTP] Block {block}/{-(-total // block_size)} ({pct}%) sent to {srv_ip}:{srv_port}",
|
||||
file=sys.stderr)
|
||||
if callback:
|
||||
callback("progress", f"{pct}%")
|
||||
|
||||
# Wait for ACK
|
||||
try:
|
||||
@@ -130,7 +133,9 @@ def _tftp_put(host: str, file_path: str, remote_name: str,
|
||||
|
||||
|
||||
def _tftp_get(host: str, remote_name: str, local_dir: str | Path,
|
||||
timeout: float = 30.0) -> tuple[bool, str, Path]:
|
||||
timeout: float = 30.0,
|
||||
callback: Callable[[str, str], None] | None = None,
|
||||
expected_size: int = 0) -> tuple[bool, str, Path]:
|
||||
"""Download a file from a TFTP server using pure Python.
|
||||
|
||||
Args:
|
||||
@@ -138,6 +143,8 @@ def _tftp_get(host: str, remote_name: str, local_dir: str | Path,
|
||||
remote_name: Remote filename.
|
||||
local_dir: Local directory to save the downloaded file.
|
||||
timeout: Socket timeout in seconds.
|
||||
callback: Optional progress callback (status, message).
|
||||
expected_size: Expected file size in bytes (for progress percentage).
|
||||
|
||||
Returns:
|
||||
(success, message, local_path).
|
||||
@@ -170,46 +177,56 @@ def _tftp_get(host: str, remote_name: str, local_dir: str | Path,
|
||||
|
||||
srv_ip, srv_port = server_addr
|
||||
block = struct.unpack("!H", data_pkt[2:4])[0]
|
||||
file_data = data_pkt[4:]
|
||||
chunk = data_pkt[4:]
|
||||
import sys
|
||||
print(f"[TFTP] DATA block 1 received from {srv_ip}:{srv_port} "
|
||||
f"({len(file_data)} bytes)", file=sys.stderr)
|
||||
print(f"[TFTP] DATA block {block} received from {srv_ip}:{srv_port} "
|
||||
f"({len(chunk)} bytes)", file=sys.stderr)
|
||||
|
||||
# ── ACK block 0 and receive subsequent blocks ──
|
||||
ack = struct.pack("!HH", _TFTP_ACK, 0)
|
||||
# ── ACK this block with the actual block number ──
|
||||
file_data = bytearray(chunk)
|
||||
ack = struct.pack("!HH", _TFTP_ACK, block)
|
||||
sock.sendto(ack, server_addr)
|
||||
|
||||
total = len(file_data)
|
||||
while len(file_data) == _TFTP_BLOCK_SIZE:
|
||||
# Wait for next DATA block
|
||||
if callback:
|
||||
pct = len(file_data) * 100 // expected_size if expected_size > 0 else -1
|
||||
callback("progress", f"{pct}%")
|
||||
|
||||
# ── Receive subsequent blocks while last chunk was full-size ──
|
||||
while len(chunk) == _TFTP_BLOCK_SIZE:
|
||||
try:
|
||||
data_pkt, _ = sock.recvfrom(516)
|
||||
if len(data_pkt) < 4:
|
||||
return False, "Incomplete DATA packet", local_path
|
||||
opcode = struct.unpack("!H", data_pkt[:2])[0]
|
||||
if opcode == _TFTP_ERROR:
|
||||
err_msg = data_pkt[4:].decode("ascii", errors="replace").rstrip("\x00")
|
||||
return False, f"TFTP error: {err_msg}", local_path
|
||||
if opcode != _TFTP_DATA:
|
||||
return False, f"Expected DATA, got opcode {opcode}", local_path
|
||||
|
||||
new_block = struct.unpack("!H", data_pkt[2:4])[0]
|
||||
chunk = data_pkt[4:]
|
||||
file_data += chunk
|
||||
total = len(file_data)
|
||||
block = new_block
|
||||
|
||||
if block % 100 == 0:
|
||||
pct = total * 100 // max(total, 1)
|
||||
print(f"[TFTP] Block {block}/{block} ({pct}%) received from {srv_ip}:{srv_port}",
|
||||
file=sys.stderr)
|
||||
print(f"[TFTP] Block {block} ({len(file_data)} bytes) "
|
||||
f"received from {srv_ip}:{srv_port}", file=sys.stderr)
|
||||
|
||||
# ACK this block
|
||||
ack = struct.pack("!HH", _TFTP_ACK, block)
|
||||
sock.sendto(ack, server_addr)
|
||||
|
||||
if callback:
|
||||
pct = len(file_data) * 100 // expected_size if expected_size > 0 else -1
|
||||
callback("progress", f"{pct}%")
|
||||
except socket.timeout:
|
||||
return False, f"Timeout waiting for DATA block {block}", local_path
|
||||
return False, f"Timeout waiting for DATA block {block + 1}", local_path
|
||||
|
||||
# ── Write file ──
|
||||
local_path.write_bytes(file_data)
|
||||
return True, f"Downloaded {total} bytes to {local_path}", local_path
|
||||
total = len(file_data)
|
||||
local_path.write_bytes(bytes(file_data))
|
||||
return True, f"Downloaded {total} bytes in {block} blocks to {local_path}", local_path
|
||||
finally:
|
||||
sock.close()
|
||||
|
||||
@@ -254,7 +271,8 @@ def tftp_upload(
|
||||
)
|
||||
|
||||
try:
|
||||
ok, msg = _tftp_put(config.zynq_ip, str(file_path), remote_name)
|
||||
ok, msg = _tftp_put(config.zynq_ip, str(file_path), remote_name,
|
||||
callback=callback)
|
||||
except OSError as e:
|
||||
return TftpResult(
|
||||
step="upload", success=False,
|
||||
@@ -279,20 +297,23 @@ def tftp_download(
|
||||
remote_name: str,
|
||||
local_dir: str | Path | None = None,
|
||||
callback: ProgressCallback = None,
|
||||
expected_size: int = 0,
|
||||
) -> TftpResult:
|
||||
"""Download a file from the Zynq device via TFTP (pure Python).
|
||||
|
||||
Args:
|
||||
config: Application configuration.
|
||||
remote_name: Remote filename on the TFTP server.
|
||||
local_dir: Local directory to save the file (default: temp directory).
|
||||
local_dir: Local directory to save the file (default: project .tmp/tftp/).
|
||||
callback: Optional progress callback.
|
||||
expected_size: Expected file size in bytes (for progress percentage).
|
||||
|
||||
Returns:
|
||||
TftpResult with download status and CRC.
|
||||
"""
|
||||
if local_dir is None:
|
||||
local_dir = tempfile.gettempdir()
|
||||
# Use project-local temp dir for cross-platform compatibility
|
||||
local_dir = Path(__file__).resolve().parent.parent / ".tmp" / "tftp"
|
||||
local_dir = Path(local_dir)
|
||||
|
||||
if callback:
|
||||
@@ -307,7 +328,8 @@ def tftp_download(
|
||||
)
|
||||
|
||||
try:
|
||||
ok, msg, local_path = _tftp_get(config.zynq_ip, remote_name, local_dir)
|
||||
ok, msg, local_path = _tftp_get(config.zynq_ip, remote_name, local_dir,
|
||||
callback=callback, expected_size=expected_size)
|
||||
except OSError as e:
|
||||
return TftpResult(
|
||||
step="download", success=False,
|
||||
|
||||
Reference in New Issue
Block a user