From fa58fe5f20153192922b3d903c6c1c0c16e6e075 Mon Sep 17 00:00:00 2001 From: Eduard Iten Date: Wed, 25 Feb 2026 11:32:55 +0100 Subject: [PATCH] sync --- buzzer_tool/core/commands/put.py | 80 +++++++++++++++++------------- buzzer_tool/core/connection.py | 85 +++++++++++++++++--------------- firmware/src/usb.c | 21 ++++---- 3 files changed, 102 insertions(+), 84 deletions(-) diff --git a/buzzer_tool/core/commands/put.py b/buzzer_tool/core/commands/put.py index de5d4d9..be570e0 100644 --- a/buzzer_tool/core/commands/put.py +++ b/buzzer_tool/core/commands/put.py @@ -1,7 +1,8 @@ -# core/commands/put.py import os import zlib import glob +import time +import sys from core.connection import BuzzerError def get_file_crc32(filepath: str) -> int: @@ -13,55 +14,66 @@ def get_file_crc32(filepath: str) -> int: return crc & 0xFFFFFFFF def execute(conn, sources: list, target: str): - """ - Lädt Dateien auf den Mikrocontroller hoch. - """ - # 1. Globbing auflösen (Notwendig für Windows, da die CMD Wildcards nicht auflöst) - resolved_files = [] - for src in sources: - matches = glob.glob(src) - if matches: - resolved_files.extend(matches) - else: - print(f"Warnung: Datei '{src}' nicht gefunden.") - + # 1. Globbing auflösen + resolved_files = [f for src in sources for f in glob.glob(src) if os.path.isfile(f)] + if not resolved_files: - print("Keine gültigen Dateien zum Übertragen gefunden.") + print("Keine gültigen Dateien gefunden.") return + total_size_all = sum(os.path.getsize(f) for f in resolved_files) + sent_all = 0 + start_time_all = time.monotonic() is_target_dir = target.endswith('/') - # Wenn mehrere Dateien angegeben sind, muss das Ziel zwingend ein Verzeichnis sein - if len(resolved_files) > 1 and not is_target_dir: - print("Fehler: Bei mehreren Quelldateien muss das Ziel ein Verzeichnis sein (mit '/' enden).") - return - - # 2. Transfer-Schleife for filepath in resolved_files: - if not os.path.isfile(filepath): - print(f"Überspringe '{filepath}' (ist keine Datei)") - continue - filename = os.path.basename(filepath) filesize = os.path.getsize(filepath) crc32 = get_file_crc32(filepath) - dest_path = f"{target}{filename}" if is_target_dir else target - size_kb = filesize / 1024 - print(f"Sende 📄 {filename} \033[90m({size_kb:.1f} KB)\033[0m nach {dest_path} ... ", end="", flush=True) + print(f"Sende 📄 {filename} ({filesize/1024:.1f} KB) -> {dest_path}") + + start_time_file = time.monotonic() + sent_file = 0 + + def progress_handler(chunk_len): + nonlocal sent_file, sent_all + sent_file += chunk_len + sent_all += chunk_len + + elapsed = time.monotonic() - start_time_file + speed = (sent_file / 1024) / elapsed if elapsed > 0 else 0 + + # Prozentberechnungen + perc_file = (sent_file / filesize) * 100 + perc_all = (sent_all / total_size_all) * 100 + + # ETA (Basierend auf Gesamtgeschwindigkeit) + elapsed_all = time.monotonic() - start_time_all + avg_speed_all = sent_all / elapsed_all if elapsed_all > 0 else 0 + eta_sec = (total_size_all - sent_all) / avg_speed_all if avg_speed_all > 0 else 0 + eta_str = f"{int(eta_sec // 60):02d}:{int(eta_sec % 60):02d}" + + # Ausgabezeile (\r überschreibt die aktuelle Zeile) + sys.stdout.write( + f"\r \033[90mProg: {perc_file:3.0f}% | Gesamt: {perc_all:3.0f}% | " + f"{speed:6.1f} KB/s | ETA: {eta_str}\033[0m" + ) + sys.stdout.flush() try: - # PUT-Befehl ohne Warten auf 'OK' senden cmd = f"put {dest_path};{filesize};{crc32}\n" conn.serial.write(cmd.encode('utf-8')) conn.serial.flush() - # Warten auf READY, Binärdaten senden und auf abschließendes OK warten - conn.send_binary(filepath) + # Binärtransfer mit unserem Handler + conn.send_binary(filepath, progress_callback=progress_handler) - print("\033[32mErfolgreich\033[0m") - except BuzzerError as e: - print(f"\n ❌ \033[31mFehler vom Controller: {e}\033[0m") + # Zeile nach Erfolg abschließen + print(f"\r \033[32mFertig: {filename} übertragen. \033[0m") except Exception as e: - print(f"\n ❌ \033[31mÜbertragungsfehler: {e}\033[0m") \ No newline at end of file + print(f"\n ❌ \033[31mFehler: {e}\033[0m") + + total_duration = time.monotonic() - start_time_all + print(f"\nAlle {len(resolved_files)} Dateien in {total_duration:.1f}s übertragen.") \ No newline at end of file diff --git a/buzzer_tool/core/connection.py b/buzzer_tool/core/connection.py index d440ea3..ddc9a8d 100644 --- a/buzzer_tool/core/connection.py +++ b/buzzer_tool/core/connection.py @@ -64,49 +64,52 @@ class BuzzerConnection: raise TimeoutError(f"Lese-Timeout ({eff_timeout}s) beim Warten auf Antwort für: '{command}'") - def send_binary(self, filepath: str, chunk_size: int = 512, timeout: float = 10.0): - """ - Überträgt eine Binärdatei in Chunks, nachdem das READY-Signal empfangen wurde. - """ - # 1. Warte auf die READY-Bestätigung vom Controller - start_time = time.time() - ready = False - while (time.time() - start_time) < timeout: - if self.serial.in_waiting > 0: - line = self.serial.readline().decode('utf-8', errors='ignore').strip() - if line == "READY": - ready = True - break - elif line.startswith("ERR"): - raise BuzzerError(f"Fehler vor Binärtransfer: {line}") - time.sleep(0.01) + def send_binary(self, filepath: str, chunk_size: int = 4096, timeout: float = 10.0, progress_callback=None): + """ + Überträgt eine Binärdatei in Chunks, nachdem das READY-Signal empfangen wurde. + """ + # 1. Warte auf die READY-Bestätigung vom Controller + start_time = time.time() + ready = False + while (time.time() - start_time) < timeout: + if self.serial.in_waiting > 0: + line = self.serial.readline().decode('utf-8', errors='ignore').strip() + if line == "READY": + ready = True + break + elif line.startswith("ERR"): + raise BuzzerError(f"Fehler vor Binärtransfer: {line}") + time.sleep(0.01) - if not ready: - raise TimeoutError("Kein READY-Signal vom Controller empfangen.") + if not ready: + raise TimeoutError("Kein READY-Signal vom Controller empfangen.") - # 2. Sende die Datei in Blöcken - file_size = os.path.getsize(filepath) - bytes_sent = 0 + # 2. Sende die Datei in Blöcken + file_size = os.path.getsize(filepath) + bytes_sent = 0 - with open(filepath, 'rb') as f: - while bytes_sent < file_size: - chunk = f.read(chunk_size) - if not chunk: - break - self.serial.write(chunk) - # Flush blockiert, bis die Daten an den OS-USB-Treiber übergeben wurden - self.serial.flush() - bytes_sent += len(chunk) + with open(filepath, 'rb') as f: + while bytes_sent < file_size: + chunk = f.read(chunk_size) + if not chunk: + break + self.serial.write(chunk) + self.serial.flush() + bytes_sent += len(chunk) + + # Callback aufrufen, falls vorhanden + if progress_callback: + progress_callback(len(chunk)) - # 3. Warte auf das finale OK (oder ERR bei CRC/Schreib-Fehlern) - start_time = time.time() - while (time.time() - start_time) < timeout: - if self.serial.in_waiting > 0: - line = self.serial.readline().decode('utf-8', errors='ignore').strip() - if line == "OK": - return True - elif line.startswith("ERR"): - raise BuzzerError(f"Fehler beim Speichern der Binärdatei: {line}") - time.sleep(0.01) + # 3. Warte auf das finale OK (oder ERR bei CRC/Schreib-Fehlern) + start_time = time.time() + while (time.time() - start_time) < timeout: + if self.serial.in_waiting > 0: + line = self.serial.readline().decode('utf-8', errors='ignore').strip() + if line == "OK": + return True + elif line.startswith("ERR"): + raise BuzzerError(f"Fehler beim Speichern der Binärdatei: {line}") + time.sleep(0.01) - raise TimeoutError("Zeitüberschreitung nach Binärtransfer (kein OK empfangen).") \ No newline at end of file + raise TimeoutError("Zeitüberschreitung nach Binärtransfer (kein OK empfangen).") \ No newline at end of file diff --git a/firmware/src/usb.c b/firmware/src/usb.c index ef6ae97..2c96317 100644 --- a/firmware/src/usb.c +++ b/firmware/src/usb.c @@ -37,7 +37,6 @@ static void cdc_acm_irq_cb(const struct device *dev, void *user_data) bool usb_wait_for_data(k_timeout_t timeout) { - /* Wartet auf das Signal aus der ISR */ return (k_sem_take(&usb_rx_sem, timeout) == 0); } @@ -71,23 +70,27 @@ void usb_write_char(uint8_t c) void usb_write_buffer(const uint8_t *buf, size_t len) { - if (!device_is_ready(cdc_dev)) { + if (!device_is_ready(cdc_dev)) + { return; } size_t written; - while (len > 0) { + while (len > 0) + { written = uart_fifo_fill(cdc_dev, buf, len); - + len -= written; buf += written; - if (len > 0) { - /* Der FIFO ist voll, aber wir haben noch Daten. - * 1. TX-Interrupt aktivieren (meldet sich, wenn wieder Platz ist) - * 2. Thread schlafen legen, bis die ISR die Semaphore gibt */ + if (len > 0) + { uart_irq_tx_enable(cdc_dev); - k_sem_take(&usb_tx_sem, K_FOREVER); + if (k_sem_take(&usb_tx_sem, K_MSEC(100)) != 0) + { + LOG_WRN("USB TX timeout - consumer not reading?"); + return; + } } } }