From fa8124f2d73ff343407faa6cf2f666705c7fb987 Mon Sep 17 00:00:00 2001 From: Tobias Leuschner Date: Sun, 17 May 2026 18:02:13 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20Fehlerbehandlung=20f=C3=BCr=20WiFi-Verb?= =?UTF-8?q?indungen=20verbessert=20und=20Fallback-AP-Start=20optimiert;=20?= =?UTF-8?q?Versionsnummer=20auf=201.0.77=20erh=C3=B6ht?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.py | 31 ++++++++++++++++++++++++------- picopy/wifi.py | 33 +++++++++++++++++++++++++-------- routes/wifi_routes.py | 2 +- 3 files changed, 50 insertions(+), 16 deletions(-) diff --git a/app.py b/app.py index 65c58d0..dab86e5 100644 --- a/app.py +++ b/app.py @@ -1,14 +1,24 @@ #!/usr/bin/env python3 """PiCopy v2 - USB Copy Service mit WiFi-Fallback AP""" +import traceback import urllib.request as _urlreq from pathlib import Path -from flask import Flask, render_template, send_file +from flask import Flask, render_template, send_file, jsonify -from picopy.config import VERSION, BASE_DIR, RAW_BASE +from picopy.config import VERSION, BASE_DIR, RAW_BASE, log + +# Absoluter Pfad zum templates/-Verzeichnis, unabhängig vom Arbeitsverzeichnis +_HERE = Path(__file__).parent +app = Flask(__name__, template_folder=str(_HERE / 'templates')) + + +@app.errorhandler(Exception) +def _handle_exception(e): + log.error('Unbehandelter Fehler:\n' + traceback.format_exc()) + return jsonify(error=str(e)), 500 -app = Flask(__name__, template_folder='templates') from routes import register_routes register_routes(app) @@ -49,14 +59,21 @@ if __name__ == '__main__': from picopy.wifi import wifi_monitor from picopy.wireguard import wg_monitor from picopy.system import update_check_loop - from picopy.config import log, load_cfg + from picopy.config import load_cfg + + # Startprüfung: Template vorhanden? + tmpl = _HERE / 'templates' / 'index.html' + if not tmpl.exists(): + log.error(f'FEHLER: Template nicht gefunden: {tmpl}') + else: + log.info(f'Template gefunden: {tmpl}') cleanup_stale_mounts() load_state() wg_update_state() - threading.Thread(target=usb_monitor, daemon=True).start() - threading.Thread(target=wifi_monitor, daemon=True).start() - threading.Thread(target=wg_monitor, daemon=True).start() + threading.Thread(target=usb_monitor, daemon=True).start() + threading.Thread(target=wifi_monitor, daemon=True).start() + threading.Thread(target=wg_monitor, daemon=True).start() threading.Thread(target=update_check_loop, daemon=True).start() if load_cfg().get('wireguard_auto') and WG_CONF.exists(): threading.Thread(target=wg_connect, daemon=True).start() diff --git a/picopy/wifi.py b/picopy/wifi.py index 4cbb967..a3897c5 100644 --- a/picopy/wifi.py +++ b/picopy/wifi.py @@ -95,12 +95,18 @@ def connect_client_wifi(ssid, password): log.info(f'Verbinde mit WiFi: {ssid}') nm('con', 'delete', NM_CLIENT_CON) time.sleep(1) + + # Scan erzwingen – sonst findet nmcli das Netz nach dem Boot nicht im Cache + log.info('WiFi-Scan läuft...') + nm('dev', 'wifi', 'rescan') + time.sleep(4) + try: - # --wait 15: nmcli gibt nach 15 s auf; subprocess-Timeout als Sicherheitsnetz + # --wait 45: genug Zeit für Scan + WPA-Handshake + DHCP r = subprocess.run( - ['nmcli', '--wait', '15', 'dev', 'wifi', 'connect', ssid, + ['nmcli', '--wait', '45', 'dev', 'wifi', 'connect', ssid, 'password', password, 'name', NM_CLIENT_CON, 'ifname', 'wlan0'], - capture_output=True, text=True, timeout=25, + capture_output=True, text=True, timeout=55, ) except subprocess.TimeoutExpired: log.warning(f'WiFi-Verbindung Timeout (SSID: {ssid})') @@ -166,29 +172,40 @@ def wifi_monitor(): _wait_for_nm() _purge_foreign_wifi_profiles() + connect_failures = 0 + while True: try: update_wifi_state() with wifi_lock: mode = wifi_state['mode'] - cfg = load_cfg() + cfg = load_cfg() + ssid = cfg.get('wifi_ssid', '') + pw = cfg.get('wifi_password', '') - if mode == 'disconnected': - ssid = cfg.get('wifi_ssid', '') - pw = cfg.get('wifi_password', '') + if mode == 'client': + connect_failures = 0 # Verbindung OK – Zähler zurücksetzen + elif mode == 'disconnected': connected = False if ssid: + log.info(f'Verbindungsversuch {connect_failures + 1}: {ssid}') connected = connect_client_wifi(ssid, pw) if connected: + connect_failures = 0 time.sleep(5) update_wifi_state() + else: + connect_failures += 1 - if not connected: + # AP erst starten wenn kein SSID konfiguriert ODER nach 2 Fehlversuchen + if not connected and (not ssid or connect_failures >= 2): ap_ssid = cfg.get('ap_ssid', 'PiCopy') ap_pw = cfg.get('ap_password', 'PiCopy123') + log.info(f'Starte Fallback-AP nach {connect_failures} Fehlversuchen') if start_ap(ap_ssid, ap_pw): + connect_failures = 0 time.sleep(3) with wifi_lock: wifi_state.update(mode='ap', ssid=ap_ssid, ip='10.42.0.1') diff --git a/routes/wifi_routes.py b/routes/wifi_routes.py index d3e1512..b692c87 100644 --- a/routes/wifi_routes.py +++ b/routes/wifi_routes.py @@ -45,7 +45,7 @@ def r_wifi_connect(): update_wifi_state() else: if ap_was_active: - start_ap(cfg.get('ap_ssid', 'PiCopy'), cfg.get('ap_password', 'PiCopy,')) + start_ap(cfg.get('ap_ssid', 'PiCopy'), cfg.get('ap_password', 'PiCopy123')) update_wifi_state() threading.Thread(target=_connect, daemon=True).start()