sync
This commit is contained in:
@@ -1,7 +1,8 @@
|
|||||||
# core/commands/put.py
|
|
||||||
import os
|
import os
|
||||||
import zlib
|
import zlib
|
||||||
import glob
|
import glob
|
||||||
|
import time
|
||||||
|
import sys
|
||||||
from core.connection import BuzzerError
|
from core.connection import BuzzerError
|
||||||
|
|
||||||
def get_file_crc32(filepath: str) -> int:
|
def get_file_crc32(filepath: str) -> int:
|
||||||
@@ -13,55 +14,66 @@ def get_file_crc32(filepath: str) -> int:
|
|||||||
return crc & 0xFFFFFFFF
|
return crc & 0xFFFFFFFF
|
||||||
|
|
||||||
def execute(conn, sources: list, target: str):
|
def execute(conn, sources: list, target: str):
|
||||||
"""
|
# 1. Globbing auflösen
|
||||||
Lädt Dateien auf den Mikrocontroller hoch.
|
resolved_files = [f for src in sources for f in glob.glob(src) if os.path.isfile(f)]
|
||||||
"""
|
|
||||||
# 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.")
|
|
||||||
|
|
||||||
if not resolved_files:
|
if not resolved_files:
|
||||||
print("Keine gültigen Dateien zum Übertragen gefunden.")
|
print("Keine gültigen Dateien gefunden.")
|
||||||
return
|
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('/')
|
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:
|
for filepath in resolved_files:
|
||||||
if not os.path.isfile(filepath):
|
|
||||||
print(f"Überspringe '{filepath}' (ist keine Datei)")
|
|
||||||
continue
|
|
||||||
|
|
||||||
filename = os.path.basename(filepath)
|
filename = os.path.basename(filepath)
|
||||||
filesize = os.path.getsize(filepath)
|
filesize = os.path.getsize(filepath)
|
||||||
crc32 = get_file_crc32(filepath)
|
crc32 = get_file_crc32(filepath)
|
||||||
|
|
||||||
dest_path = f"{target}{filename}" if is_target_dir else target
|
dest_path = f"{target}{filename}" if is_target_dir else target
|
||||||
|
|
||||||
size_kb = filesize / 1024
|
print(f"Sende 📄 {filename} ({filesize/1024:.1f} KB) -> {dest_path}")
|
||||||
print(f"Sende 📄 {filename} \033[90m({size_kb:.1f} KB)\033[0m nach {dest_path} ... ", end="", flush=True)
|
|
||||||
|
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:
|
try:
|
||||||
# PUT-Befehl ohne Warten auf 'OK' senden
|
|
||||||
cmd = f"put {dest_path};{filesize};{crc32}\n"
|
cmd = f"put {dest_path};{filesize};{crc32}\n"
|
||||||
conn.serial.write(cmd.encode('utf-8'))
|
conn.serial.write(cmd.encode('utf-8'))
|
||||||
conn.serial.flush()
|
conn.serial.flush()
|
||||||
|
|
||||||
# Warten auf READY, Binärdaten senden und auf abschließendes OK warten
|
# Binärtransfer mit unserem Handler
|
||||||
conn.send_binary(filepath)
|
conn.send_binary(filepath, progress_callback=progress_handler)
|
||||||
|
|
||||||
print("\033[32mErfolgreich\033[0m")
|
# Zeile nach Erfolg abschließen
|
||||||
except BuzzerError as e:
|
print(f"\r \033[32mFertig: {filename} übertragen. \033[0m")
|
||||||
print(f"\n ❌ \033[31mFehler vom Controller: {e}\033[0m")
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"\n ❌ \033[31mÜbertragungsfehler: {e}\033[0m")
|
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.")
|
||||||
@@ -64,49 +64,52 @@ class BuzzerConnection:
|
|||||||
|
|
||||||
raise TimeoutError(f"Lese-Timeout ({eff_timeout}s) beim Warten auf Antwort für: '{command}'")
|
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):
|
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.
|
Überträgt eine Binärdatei in Chunks, nachdem das READY-Signal empfangen wurde.
|
||||||
"""
|
"""
|
||||||
# 1. Warte auf die READY-Bestätigung vom Controller
|
# 1. Warte auf die READY-Bestätigung vom Controller
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
ready = False
|
ready = False
|
||||||
while (time.time() - start_time) < timeout:
|
while (time.time() - start_time) < timeout:
|
||||||
if self.serial.in_waiting > 0:
|
if self.serial.in_waiting > 0:
|
||||||
line = self.serial.readline().decode('utf-8', errors='ignore').strip()
|
line = self.serial.readline().decode('utf-8', errors='ignore').strip()
|
||||||
if line == "READY":
|
if line == "READY":
|
||||||
ready = True
|
ready = True
|
||||||
break
|
break
|
||||||
elif line.startswith("ERR"):
|
elif line.startswith("ERR"):
|
||||||
raise BuzzerError(f"Fehler vor Binärtransfer: {line}")
|
raise BuzzerError(f"Fehler vor Binärtransfer: {line}")
|
||||||
time.sleep(0.01)
|
time.sleep(0.01)
|
||||||
|
|
||||||
if not ready:
|
if not ready:
|
||||||
raise TimeoutError("Kein READY-Signal vom Controller empfangen.")
|
raise TimeoutError("Kein READY-Signal vom Controller empfangen.")
|
||||||
|
|
||||||
# 2. Sende die Datei in Blöcken
|
# 2. Sende die Datei in Blöcken
|
||||||
file_size = os.path.getsize(filepath)
|
file_size = os.path.getsize(filepath)
|
||||||
bytes_sent = 0
|
bytes_sent = 0
|
||||||
|
|
||||||
with open(filepath, 'rb') as f:
|
with open(filepath, 'rb') as f:
|
||||||
while bytes_sent < file_size:
|
while bytes_sent < file_size:
|
||||||
chunk = f.read(chunk_size)
|
chunk = f.read(chunk_size)
|
||||||
if not chunk:
|
if not chunk:
|
||||||
break
|
break
|
||||||
self.serial.write(chunk)
|
self.serial.write(chunk)
|
||||||
# Flush blockiert, bis die Daten an den OS-USB-Treiber übergeben wurden
|
self.serial.flush()
|
||||||
self.serial.flush()
|
bytes_sent += len(chunk)
|
||||||
bytes_sent += len(chunk)
|
|
||||||
|
|
||||||
# 3. Warte auf das finale OK (oder ERR bei CRC/Schreib-Fehlern)
|
# Callback aufrufen, falls vorhanden
|
||||||
start_time = time.time()
|
if progress_callback:
|
||||||
while (time.time() - start_time) < timeout:
|
progress_callback(len(chunk))
|
||||||
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).")
|
# 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).")
|
||||||
@@ -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)
|
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);
|
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)
|
void usb_write_buffer(const uint8_t *buf, size_t len)
|
||||||
{
|
{
|
||||||
if (!device_is_ready(cdc_dev)) {
|
if (!device_is_ready(cdc_dev))
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t written;
|
size_t written;
|
||||||
while (len > 0) {
|
while (len > 0)
|
||||||
|
{
|
||||||
written = uart_fifo_fill(cdc_dev, buf, len);
|
written = uart_fifo_fill(cdc_dev, buf, len);
|
||||||
|
|
||||||
len -= written;
|
len -= written;
|
||||||
buf += written;
|
buf += written;
|
||||||
|
|
||||||
if (len > 0) {
|
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 */
|
|
||||||
uart_irq_tx_enable(cdc_dev);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user