🛠️ "Implementiere TP-Link Tapo P110 Unterstützung für Druckerüberwachung und -steuerung"

- Aktualisiere die `check_printer_status()` Funktion zur Verwendung des PyP100-Moduls für die Tapo-Steckdosen.
- Füge neue API-Endpunkte hinzu: `test-tapo` für die Verbindungstests einzelner Drucker und `test-all-tapo` für Massentests.
- Verbessere die Fehlerbehandlung und Logging für Tapo-Verbindungen.
- Aktualisiere die Benutzeroberfläche, um den Datei-Upload als optional zu kennzeichnen.
- Implementiere umfassende Tests für die Tapo-Verbindungen in `debug_drucker_erkennung.py` und verbessere die Validierung der Konfiguration in `job_scheduler.py`.
This commit is contained in:
2025-05-29 21:19:30 +02:00
parent da1d531c16
commit de1b87f833
8 changed files with 585 additions and 178 deletions

View File

@@ -1281,7 +1281,7 @@ def kiosk_restart_system():
@measure_execution_time(logger=printers_logger, task_name="Drucker-Status-Prüfung")
def check_printer_status(ip_address: str, timeout: int = 7) -> Tuple[str, bool]:
"""
Überprüft den Status eines Druckers über Steckdosenabfrage mit Timeout.
Überprüft den Status eines Druckers über TP-Link Tapo P110-Steckdosenabfrage.
Args:
ip_address: IP-Adresse der Drucker-Steckdose
@@ -1303,7 +1303,7 @@ def check_printer_status(ip_address: str, timeout: int = 7) -> Tuple[str, bool]:
printers_logger.warning(f"Ungültige IP-Adresse: {ip_address}")
return "offline", False
# Zuerst prüfen, ob die Steckdose erreichbar ist
# Zuerst prüfen, ob die Steckdose erreichbar ist (Ping)
if os.name == 'nt': # Windows
cmd = ['ping', '-n', '1', '-w', str(timeout * 1000), ip_address.strip()]
else: # Unix/Linux/macOS
@@ -1326,7 +1326,7 @@ def check_printer_status(ip_address: str, timeout: int = 7) -> Tuple[str, bool]:
printers_logger.debug(f"Ping fehlgeschlagen für {ip_address} (Return Code: {result.returncode})")
return "offline", False
# Jetzt den tatsächlichen Steckdosenstatus abfragen
# Drucker-Daten aus Datenbank holen für Anmeldedaten
db_session = get_db_session()
printer = db_session.query(Printer).filter(Printer.plug_ip == ip_address).first()
@@ -1335,46 +1335,36 @@ def check_printer_status(ip_address: str, timeout: int = 7) -> Tuple[str, bool]:
db_session.close()
return "offline", False
# Smart Plug Status prüfen
import requests
from requests.exceptions import RequestException
# Standardwerte aus der Datenbank verwenden
username = printer.plug_username
password = printer.plug_password
# TP-Link Tapo P110 Status mit PyP100 prüfen
try:
# Für TP-Link Smart Plugs oder kompatible Steckdosen
auth = (username, password)
response = requests.get(f"http://{ip_address}/status", auth=auth, timeout=timeout)
from PyP100 import PyP110
if response.status_code == 200:
try:
status_data = response.json()
# Überprüfen ob die Steckdose eingeschaltet ist
if 'system' in status_data and 'get_sysinfo' in status_data['system']:
if status_data['system']['get_sysinfo'].get('relay_state') == 1:
printers_logger.debug(f"Steckdose {ip_address} ist eingeschaltet")
db_session.close()
return "online", True
except (ValueError, KeyError) as e:
printers_logger.debug(f"Fehler beim Parsen der Steckdosen-Antwort: {str(e)}")
# Tapo-Steckdose verbinden
p110 = PyP110.P110(ip_address, printer.plug_username, printer.plug_password)
p110.handshake() # Authentifizierung
p110.login() # Login
# Zweiter Versuch mit einfacher GET-Anfrage
response = requests.get(f"http://{ip_address}", auth=auth, timeout=timeout)
if response.status_code == 200:
printers_logger.debug(f"Steckdose {ip_address} antwortet auf HTTP-Anfrage")
# Wenn wir hier ankommen, ist die Steckdose online, aber wir wissen nicht sicher, ob sie eingeschaltet ist
# Da wir nur die Verfügbarkeit prüfen, nehmen wir an, dass sie aktiv ist, wenn sie antwortet
db_session.close()
# Geräteinformationen abrufen
device_info = p110.getDeviceInfo()
device_on = device_info.get('device_on', False)
db_session.close()
if device_on:
printers_logger.debug(f"Tapo-Steckdose {ip_address} ist eingeschaltet - Drucker online")
return "online", True
else:
printers_logger.debug(f"Tapo-Steckdose {ip_address} ist ausgeschaltet - Drucker offline")
return "offline", False
except RequestException as e:
printers_logger.debug(f"Fehler bei HTTP-Anfrage an Steckdose {ip_address}: {str(e)}")
# Wenn beide API-Anfragen fehlschlagen, können wir annehmen, dass die Steckdose nicht eingeschaltet ist
db_session.close()
return "offline", False
except ImportError:
printers_logger.error("PyP100-Modul nicht verfügbar - kann Tapo-Steckdose nicht abfragen")
db_session.close()
return "offline", False
except Exception as e:
printers_logger.debug(f"Fehler bei Tapo-Steckdosen-Abfrage {ip_address}: {str(e)}")
db_session.close()
return "offline", False
except subprocess.TimeoutExpired:
printers_logger.warning(f"Ping-Timeout für Drucker {ip_address} nach {timeout} Sekunden")
@@ -3501,14 +3491,45 @@ def create_user_api():
@app.route("/api/admin/printers/<int:printer_id>/toggle", methods=["POST"])
@login_required
def toggle_printer_power(printer_id):
"""Schaltet einen Drucker ein oder aus (nur für Admins)."""
"""
Schaltet einen Drucker über die zugehörige Steckdose ein/aus.
"""
if not current_user.is_admin:
return jsonify({"error": "Nur Administratoren können Drucker steuern"}), 403
return jsonify({"error": "Administratorrechte erforderlich"}), 403
try:
data = request.json
power_on = data.get("power_on", True)
data = request.get_json()
state = data.get("state", True) # Standard: einschalten
# Steckdose schalten
from utils.job_scheduler import toggle_plug
success = toggle_plug(printer_id, state)
if success:
action = "eingeschaltet" if state else "ausgeschaltet"
return jsonify({
"success": True,
"message": f"Drucker erfolgreich {action}",
"printer_id": printer_id,
"state": state
})
else:
return jsonify({
"error": "Fehler beim Schalten der Steckdose"
}), 500
except Exception as e:
printers_logger.error(f"Fehler beim Schalten von Drucker {printer_id}: {str(e)}")
return jsonify({"error": "Interner Serverfehler"}), 500
@app.route("/api/admin/printers/<int:printer_id>/test-tapo", methods=["POST"])
@login_required
@admin_required
def test_printer_tapo_connection(printer_id):
"""
Testet die Tapo-Steckdosen-Verbindung für einen Drucker.
"""
try:
db_session = get_db_session()
printer = db_session.query(Printer).get(printer_id)
@@ -3516,33 +3537,114 @@ def toggle_printer_power(printer_id):
db_session.close()
return jsonify({"error": "Drucker nicht gefunden"}), 404
# Steckdose schalten
from utils.job_scheduler import toggle_plug
success = toggle_plug(printer_id, power_on)
if success:
# Status in der Datenbank aktualisieren
printer.status = "available" if power_on else "offline"
printer.active = power_on
db_session.commit()
action = "eingeschaltet" if power_on else "ausgeschaltet"
printers_logger.info(f"Drucker {printer.name} {action} von Admin {current_user.id}")
if not printer.plug_ip or not printer.plug_username or not printer.plug_password:
db_session.close()
return jsonify({
"success": True,
"message": f"Drucker erfolgreich {action}",
"status": printer.status
})
else:
db_session.close()
return jsonify({"error": "Fehler beim Schalten der Steckdose"}), 500
"error": "Unvollständige Tapo-Konfiguration",
"missing": [
key for key, value in {
"plug_ip": printer.plug_ip,
"plug_username": printer.plug_username,
"plug_password": printer.plug_password
}.items() if not value
]
}), 400
db_session.close()
# Tapo-Verbindung testen
from utils.job_scheduler import test_tapo_connection
test_result = test_tapo_connection(
printer.plug_ip,
printer.plug_username,
printer.plug_password
)
return jsonify({
"printer_id": printer_id,
"printer_name": printer.name,
"tapo_test": test_result
})
except Exception as e:
printers_logger.error(f"Fehler beim Schalten des Druckers {printer_id}: {str(e)}")
return jsonify({"error": "Interner Serverfehler"}), 500
printers_logger.error(f"Fehler beim Testen der Tapo-Verbindung für Drucker {printer_id}: {str(e)}")
return jsonify({"error": "Interner Serverfehler beim Verbindungstest"}), 500
@app.route("/api/admin/printers/test-all-tapo", methods=["POST"])
@login_required
@admin_required
def test_all_printers_tapo_connection():
"""
Testet die Tapo-Steckdosen-Verbindung für alle Drucker.
Nützlich für Diagnose und Setup-Validierung.
"""
try:
db_session = get_db_session()
printers = db_session.query(Printer).filter(Printer.active == True).all()
db_session.close()
if not printers:
return jsonify({
"message": "Keine aktiven Drucker gefunden",
"results": []
})
# Alle Drucker testen
from utils.job_scheduler import test_tapo_connection
results = []
for printer in printers:
result = {
"printer_id": printer.id,
"printer_name": printer.name,
"plug_ip": printer.plug_ip,
"has_config": bool(printer.plug_ip and printer.plug_username and printer.plug_password)
}
if result["has_config"]:
# Tapo-Verbindung testen
test_result = test_tapo_connection(
printer.plug_ip,
printer.plug_username,
printer.plug_password
)
result["tapo_test"] = test_result
else:
result["tapo_test"] = {
"success": False,
"error": "Unvollständige Tapo-Konfiguration",
"device_info": None,
"status": "unconfigured"
}
result["missing_config"] = [
key for key, value in {
"plug_ip": printer.plug_ip,
"plug_username": printer.plug_username,
"plug_password": printer.plug_password
}.items() if not value
]
results.append(result)
# Zusammenfassung erstellen
total_printers = len(results)
successful_connections = sum(1 for r in results if r["tapo_test"]["success"])
configured_printers = sum(1 for r in results if r["has_config"])
return jsonify({
"summary": {
"total_printers": total_printers,
"configured_printers": configured_printers,
"successful_connections": successful_connections,
"success_rate": round(successful_connections / total_printers * 100, 1) if total_printers > 0 else 0
},
"results": results
})
except Exception as e:
printers_logger.error(f"Fehler beim Testen aller Tapo-Verbindungen: {str(e)}")
return jsonify({"error": "Interner Serverfehler beim Massentest"}), 500
# ===== ADMIN FORM ENDPOINTS =====
@app.route("/admin/users/create", methods=["POST"])