From 9502a9b3afaf3d9b59d83cdfc2da847263cc41f1 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 13 Mar 2025 09:56:03 +0100 Subject: [PATCH] steckdose timeout --- backend/app.py | 121 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 98 insertions(+), 23 deletions(-) diff --git a/backend/app.py b/backend/app.py index 59117a5..7d2e356 100755 --- a/backend/app.py +++ b/backend/app.py @@ -654,8 +654,17 @@ def get_socket_uptime_events(socket_id=None, limit=100): return [dict(row) for row in rows] -def check_socket_connection(socket_id): - """Überprüft die Verbindung zu einer Steckdose und aktualisiert den Status""" +def check_socket_connection(socket_id, timeout=8): + """ + Überprüft die Verbindung zu einer Steckdose und aktualisiert den Status. + + Args: + socket_id: ID der Steckdose + timeout: Timeout in Sekunden, nach dem die Verbindung als fehlgeschlagen gilt + + Returns: + True wenn die Steckdose online ist, sonst False + """ socket = get_socket_by_id(socket_id) if not socket or not socket['ip_address']: return False @@ -664,7 +673,8 @@ def check_socket_connection(socket_id): last_seen = socket.get('last_seen') try: - device = get_socket_device(socket['ip_address']) + # Verwende den Timeout-Parameter für die Geräteverbindung + device = get_socket_device(socket['ip_address'], timeout=timeout) if device: # Verbindung erfolgreich if previous_status != 'online': @@ -672,14 +682,18 @@ def check_socket_connection(socket_id): duration = None if previous_status == 'offline' and last_seen: # Berechne die Dauer des Ausfalls - offline_since = datetime.datetime.fromisoformat(last_seen) - now = datetime.datetime.utcnow() - duration = int((now - offline_since).total_seconds()) + try: + offline_since = datetime.datetime.fromisoformat(last_seen) + now = datetime.datetime.utcnow() + duration = int((now - offline_since).total_seconds()) + except (ValueError, TypeError): + # Wenn das Datum nicht geparst werden kann + duration = None log_socket_connection_event(socket_id, 'online', duration) return True else: - # Keine Verbindung möglich + # Keine Verbindung möglich oder Timeout if previous_status != 'offline': # Status hat sich von online/unknown auf offline geändert log_socket_connection_event(socket_id, 'offline') @@ -691,20 +705,66 @@ def check_socket_connection(socket_id): return False # Steckdosen-Steuerung mit PyP100 -def get_socket_device(ip_address): +def get_socket_device(ip_address, timeout=8): + """ + Stellt eine Verbindung zu einer Tapo P100-Steckdose her, mit einem konfigurierbaren Timeout. + + Args: + ip_address: IP-Adresse der Steckdose + timeout: Timeout in Sekunden, nach dem die Verbindung als fehlgeschlagen gilt + + Returns: + Das PyP100-Geräteobjekt bei erfolgreicher Verbindung, sonst None + """ try: - device = PyP100.P100(ip_address, TAPO_USERNAME, TAPO_PASSWORD) - device.handshake() # Erstellt die erforderlichen Cookies - device.login() # Sendet Anmeldedaten und erstellt AES-Schlüssel - app.logger.info(f"PyP100 Verbindung zu {ip_address} hergestellt") - return device + # Nutze Threading mit Timeout für die Verbindung + import threading + import queue + + result_queue = queue.Queue() + + def connect_with_timeout(): + try: + device = PyP100.P100(ip_address, TAPO_USERNAME, TAPO_PASSWORD) + device.handshake() # Erstellt die erforderlichen Cookies + device.login() # Sendet Anmeldedaten und erstellt AES-Schlüssel + result_queue.put(device) + except Exception as e: + app.logger.error(f"Fehler bei der Anmeldung an P100-Gerät {ip_address}: {e}") + result_queue.put(None) + + # Starte den Verbindungsversuch in einem Thread + connect_thread = threading.Thread(target=connect_with_timeout) + connect_thread.daemon = True + connect_thread.start() + + # Warte mit Timeout auf das Ergebnis + try: + device = result_queue.get(timeout=timeout) + if device: + app.logger.info(f"PyP100 Verbindung zu {ip_address} hergestellt") + return device + except queue.Empty: + app.logger.error(f"Timeout bei der Verbindung zu {ip_address} nach {timeout} Sekunden") + return None + except Exception as e: - app.logger.error(f"Fehler bei der Anmeldung an P100-Gerät {ip_address}: {e}") + app.logger.error(f"Unerwarteter Fehler bei der Anmeldung an P100-Gerät {ip_address}: {e}") return None -def turn_on_socket(ip_address): +def turn_on_socket(ip_address, timeout=8): + """ + Schaltet eine Steckdose ein mit konfiguriertem Timeout. + + Args: + ip_address: IP-Adresse der Steckdose + timeout: Timeout in Sekunden für die Verbindung + + Returns: + True bei Erfolg, False bei Fehlern oder Timeout + """ try: - device = get_socket_device(ip_address) + device = get_socket_device(ip_address, timeout=timeout) if device: device.turnOn() app.logger.info(f"P100-Steckdose {ip_address} eingeschaltet") @@ -714,9 +774,19 @@ def turn_on_socket(ip_address): app.logger.error(f"Fehler beim Einschalten der P100-Steckdose {ip_address}: {e}") return False -def turn_off_socket(ip_address): +def turn_off_socket(ip_address, timeout=8): + """ + Schaltet eine Steckdose aus mit konfiguriertem Timeout. + + Args: + ip_address: IP-Adresse der Steckdose + timeout: Timeout in Sekunden für die Verbindung + + Returns: + True bei Erfolg, False bei Fehlern oder Timeout + """ try: - device = get_socket_device(ip_address) + device = get_socket_device(ip_address, timeout=timeout) if device: device.turnOff() app.logger.info(f"P100-Steckdose {ip_address} ausgeschaltet") @@ -1405,16 +1475,18 @@ def check_jobs(): app.logger.info(f"{len(expired_jobs)} abgelaufene Jobs überprüft, {handled_jobs} Steckdosen aktualisiert.") def check_socket_connections(): - """Überprüft periodisch die Verbindung zu allen Steckdosen.""" + """Überprüft periodisch die Verbindung zu allen Steckdosen mit 8-Sekunden-Timeout.""" with app.app_context(): sockets = get_all_sockets() app.logger.info(f"Überprüfe Verbindungsstatus von {len(sockets)} Steckdosen") online_count = 0 offline_count = 0 + skipped_count = 0 for socket in sockets: if not socket['ip_address']: + skipped_count += 1 continue # Überspringe Steckdosen ohne IP-Adresse is_online = check_socket_connection(socket['id']) @@ -1422,16 +1494,17 @@ def check_socket_connections(): online_count += 1 else: offline_count += 1 + app.logger.warning(f"Steckdose {socket['name']} ({socket['ip_address']}) ist nicht erreichbar") - app.logger.info(f"Verbindungsüberprüfung abgeschlossen: {online_count} online, {offline_count} offline") + app.logger.info(f"Verbindungsüberprüfung abgeschlossen: {online_count} online, {offline_count} offline, {skipped_count} übersprungen") # Hintergrund-Thread für das Job-Polling und Steckdosen-Monitoring def background_job_checker(): """Hintergrund-Thread, der regelmäßig abgelaufene Jobs und Steckdosenverbindungen überprüft.""" app.logger.info("Starte Hintergrund-Thread für Job-Überprüfung und Steckdosen-Monitoring") - # Standardintervall für Socket-Überprüfungen (5 Minuten) - socket_check_interval = int(os.environ.get('SOCKET_CHECK_INTERVAL', '300')) + # Standardintervall für Socket-Überprüfungen (2 Minuten) + socket_check_interval = int(os.environ.get('SOCKET_CHECK_INTERVAL', '120')) last_socket_check = 0 while True: @@ -1439,11 +1512,13 @@ def background_job_checker(): # Überprüfe Jobs bei jedem Durchlauf check_jobs() - # Überprüfe Steckdosen nur in längeren Intervallen + # Überprüfe Steckdosen in regelmäßigen Intervallen current_time = time.time() if current_time - last_socket_check >= socket_check_interval: + # Socket-Überprüfung mit 8-Sekunden-Timeout pro Gerät check_socket_connections() last_socket_check = current_time + app.logger.info(f"Nächste Socket-Überprüfung in {socket_check_interval} Sekunden") except Exception as e: app.logger.error(f"Fehler im Hintergrund-Thread: {e}")