🛠️ "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:
parent
da1d531c16
commit
de1b87f833
@ -1,3 +1,106 @@
|
||||
# MYP Platform - Behobene Fehler und Verbesserungen
|
||||
|
||||
## 🎯 **KRITISCH BEHOBEN: 2025-05-29 21:16 - Druckererkennung mit TP-Link Tapo P110-Steckdosen** ✅
|
||||
|
||||
### Problem
|
||||
Die Erkennung der "Drucker" (TP-Link Tapo P110-Steckdosen) funktionierte nicht zuverlässig:
|
||||
- Veraltete HTTP-basierte API-Anfragen anstatt des PyP100-Moduls
|
||||
- Fehlende Unterstützung für das spezielle Tapo-Protokoll
|
||||
- Keine zuverlässige Steckdosen-Status-Erkennung
|
||||
- Unvollständige Fehlerdiagnose-Tools
|
||||
|
||||
### Behebung durchgeführt
|
||||
|
||||
#### 1. **PyP100-Modul korrekt implementiert**
|
||||
- **`utils/printer_monitor.py`**:
|
||||
- `_check_outlet_status()` komplett überarbeitet für PyP100
|
||||
- `_turn_outlet_off()` modernisiert mit Tapo P110-Unterstützung
|
||||
- Direkte Verwendung von `PyP110.P110()` mit `handshake()` und `login()`
|
||||
|
||||
- **`app.py`**:
|
||||
- `check_printer_status()` Funktion modernisiert für Tapo P110
|
||||
- Ersetzt HTTP-Anfragen durch PyP100-Modul
|
||||
- Verbesserte Fehlerbehandlung und Logging
|
||||
|
||||
- **`utils/job_scheduler.py`**:
|
||||
- `toggle_plug()` verbessert mit Konfigurationvalidierung
|
||||
- Bessere Fehlerbehandlung und detailliertes Logging
|
||||
- Neue `test_tapo_connection()` Funktion hinzugefügt
|
||||
|
||||
#### 2. **Neue Test- und Debug-Funktionen**
|
||||
- **API-Routen hinzugefügt:**
|
||||
- `POST /api/admin/printers/<id>/test-tapo` - Einzeltest einer Steckdose
|
||||
- `POST /api/admin/printers/test-all-tapo` - Massentest aller Steckdosen
|
||||
- Beide nur für Administratoren verfügbar
|
||||
|
||||
- **Debug-Skript erweitert:**
|
||||
- `utils/debug_drucker_erkennung.py` um `test_tapo_connections()` erweitert
|
||||
- Detaillierte Fehleranalyse mit spezifischen Ursachen
|
||||
- Zusammenfassung mit Erfolgsrate und Empfehlungen
|
||||
|
||||
#### 3. **Verbesserte Fehlerdiagnose**
|
||||
- **Detaillierte Logging-Nachrichten:**
|
||||
- Emojis für bessere Lesbarkeit (✅ ❌ ⚠️ 🔐 🌐)
|
||||
- Spezifische Fehlermeldungen für verschiedene Verbindungsprobleme
|
||||
- Konfigurationvalidierung vor Verbindungsversuchen
|
||||
|
||||
- **Erweiterte Geräteinformationen:**
|
||||
- Gerätename (nickname) aus Tapo-Steckdose
|
||||
- Ein/Aus-Status mit device_on
|
||||
- Betriebszeit und Stromverbrauch (falls verfügbar)
|
||||
|
||||
### Technische Details
|
||||
|
||||
#### Ersetzt
|
||||
```python
|
||||
# ALT: HTTP-Anfragen mit Basic Auth
|
||||
response = requests.get(f"http://{ip}/status", auth=(user, pass))
|
||||
```
|
||||
|
||||
#### Durch
|
||||
```python
|
||||
# NEU: PyP100-Modul mit Tapo-Protokoll
|
||||
p110 = PyP110.P110(ip_address, username, password)
|
||||
p110.handshake() # Authentifizierung
|
||||
p110.login() # Login
|
||||
device_info = p110.getDeviceInfo()
|
||||
status = "on" if device_info.get('device_on', False) else "off"
|
||||
```
|
||||
|
||||
### Verwendung
|
||||
|
||||
#### Test einer einzelnen Steckdose
|
||||
```python
|
||||
from utils.job_scheduler import test_tapo_connection
|
||||
result = test_tapo_connection("192.168.1.100", "username", "password")
|
||||
print(f"Verbindung: {'✅' if result['success'] else '❌'}")
|
||||
print(f"Status: {result['status']}")
|
||||
```
|
||||
|
||||
#### Test aller Drucker via API
|
||||
```bash
|
||||
curl -X POST http://localhost:5000/api/admin/printers/test-all-tapo \
|
||||
-H "Authorization: Bearer <admin-token>"
|
||||
```
|
||||
|
||||
#### Debug-Kommandos
|
||||
```bash
|
||||
# Vollständige Diagnose inkl. Tapo-Tests
|
||||
python utils/debug_drucker_erkennung.py
|
||||
|
||||
# Test der Steckdosen-Initialisierung
|
||||
curl -X POST http://localhost:5000/api/printers/monitor/initialize-outlets
|
||||
```
|
||||
|
||||
### Ergebnis
|
||||
✅ **Zuverlässige Druckererkennung über TP-Link Tapo P110-Steckdosen**
|
||||
✅ **Korrekte Verwendung des PyP100-Moduls**
|
||||
✅ **Umfassende Test- und Debug-Tools**
|
||||
✅ **Detaillierte Fehlerdiagnose und Logging**
|
||||
✅ **Production-ready mit Admin-API-Endpunkten**
|
||||
|
||||
---
|
||||
|
||||
# JavaScript-Fehler behoben - MYP Platform
|
||||
|
||||
## Übersicht der behobenen Probleme
|
||||
@ -319,4 +422,101 @@ Falsche Verwendung des Rate-Limiting-Decorators:
|
||||
- `blueprints/printer_monitor.py` - Decorator-Syntax korrigiert
|
||||
- `docs/live_drucker_system.md` - Dokumentation aktualisiert
|
||||
|
||||
---
|
||||
---
|
||||
|
||||
## 2025-05-29 20:55 - ThreadPoolExecutor und Rate-Limiting-Probleme behoben
|
||||
|
||||
### Problem
|
||||
Das Live-Drucker-Monitor-System warf zwei kritische Fehler:
|
||||
1. **ThreadPoolExecutor-Fehler**: "max_workers must be greater than 0" wenn keine aktiven Drucker vorhanden sind
|
||||
2. **Rate-Limiting**: Die Limits waren zu niedrig gesetzt:
|
||||
- `printer_monitor_live`: nur 5 Requests/60s
|
||||
- `printer_monitor_summary`: nur 10 Requests/30s
|
||||
|
||||
### Ursache
|
||||
1. **ThreadPoolExecutor**: Der Code versuchte einen ThreadPool mit `min(len(printers), 8)` Workers zu erstellen, was bei 0 Druckern zu max_workers=0 führte
|
||||
2. **Rate-Limiting**: Die Limits waren zu niedrig gesetzt:
|
||||
- `printer_monitor_live`: nur 5 Requests/60s
|
||||
- `printer_monitor_summary`: nur 10 Requests/30s
|
||||
|
||||
### Lösung
|
||||
1. **ThreadPoolExecutor-Fix** in `utils/printer_monitor.py` und `app.py`:
|
||||
```python
|
||||
# Fehlerhafte Version:
|
||||
with ThreadPoolExecutor(max_workers=min(len(printers), 8)) as executor:
|
||||
|
||||
# Korrigierte Version:
|
||||
if not printers:
|
||||
monitor_logger.info("ℹ️ Keine aktiven Drucker gefunden")
|
||||
return status_dict
|
||||
|
||||
max_workers = min(max(len(printers), 1), 8)
|
||||
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
||||
```
|
||||
|
||||
2. **Rate-Limiting gelockert** in `utils/rate_limiter.py`:
|
||||
```python
|
||||
# Alte Limits:
|
||||
'printer_monitor_live': RateLimit(5, 60, "..."),
|
||||
'printer_monitor_summary': RateLimit(10, 30, "..."),
|
||||
|
||||
# Neue Limits:
|
||||
'printer_monitor_live': RateLimit(30, 60, "..."),
|
||||
'printer_monitor_summary': RateLimit(60, 60, "..."),
|
||||
```
|
||||
|
||||
### Auswirkungen
|
||||
- ✅ Keine ThreadPoolExecutor-Fehler mehr bei leeren Drucker-Listen
|
||||
- ✅ Live-Monitoring funktioniert auch ohne konfigurierte Drucker
|
||||
- ✅ Rate-Limits ermöglichen häufigere Live-Updates
|
||||
- ✅ Bessere Logging-Ausgaben für Debugging
|
||||
- ✅ Robustere Fehlerbehandlung
|
||||
|
||||
### Getestete Szenarien
|
||||
- System-Start ohne konfigurierte Drucker
|
||||
- Live-Status-Updates mit häufigen API-Aufrufen
|
||||
- Parallel-Status-Checks mit verschiedenen Drucker-Anzahlen
|
||||
|
||||
---
|
||||
|
||||
## 2025-05-29 17:45 - Live-Drucker-Überwachungssystem implementiert
|
||||
|
||||
### Implementierte Features
|
||||
1. **PrinterMonitor-Klasse** (`utils/printer_monitor.py`)
|
||||
- Multi-Level-Caching (Session 30s, DB 5min)
|
||||
- Thread-sichere Implementierung
|
||||
- Parallele Drucker-Abfragen via ThreadPoolExecutor
|
||||
|
||||
2. **Automatische Steckdosen-Initialisierung**
|
||||
- Beim Systemstart werden alle Steckdosen ausgeschaltet
|
||||
- Fehlertolerante Implementierung mit detailliertem Logging
|
||||
- Manuelle Admin-Initialisierung möglich
|
||||
|
||||
3. **Smart-Plug-Integration**
|
||||
- Unterstützung für TP-Link Tapo und generische APIs
|
||||
- Ping-Tests + HTTP-Status-Abfragen
|
||||
- Multiple Endpunkt-Tests mit Fallbacks
|
||||
|
||||
4. **API-Endpunkte**
|
||||
- `GET /api/printers/monitor/live-status` - Live-Status mit Caching
|
||||
- `GET /api/printers/monitor/summary` - Schnelle Status-Übersicht
|
||||
- `POST /api/printers/monitor/clear-cache` - Cache-Verwaltung
|
||||
- `POST /api/printers/monitor/initialize-outlets` - Admin-Initialisierung
|
||||
|
||||
5. **Frontend-Integration**
|
||||
- JavaScript PrinterMonitor-Klasse (`static/js/printer_monitor.js`)
|
||||
- Auto-Start auf relevanten Seiten
|
||||
- Event-basierte Updates mit adaptiven Intervallen
|
||||
|
||||
6. **Status-Kategorien**
|
||||
- Online, Standby, Offline, Unreachable, Unconfigured
|
||||
- Umfassende Status-Verfolgung mit visuellen Indikatoren
|
||||
|
||||
7. **Performance-Features**
|
||||
- Parallele Abfragen (max 8 Workers)
|
||||
- Multi-Level-Caching
|
||||
- Adaptive Timeouts und Exponential Backoff
|
||||
- Sichtbarkeits-basierte Update-Intervalle
|
||||
|
||||
### Systemdokumentation
|
||||
Vollständige Dokumentation erstellt in `docs/live_drucker_system.md` mit Architektur, API-Beispielen und Troubleshooting-Guide.
|
@ -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"])
|
||||
|
Binary file not shown.
Binary file not shown.
@ -594,7 +594,7 @@
|
||||
<svg class="w-5 h-5 mr-2 text-mercedes-blue" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"/>
|
||||
</svg>
|
||||
3D-Datei hochladen
|
||||
3D-Datei hochladen (optional)
|
||||
</h3>
|
||||
|
||||
<div id="file-upload-container">
|
||||
@ -743,8 +743,8 @@
|
||||
email: false,
|
||||
printer_id: false,
|
||||
duration_min: false,
|
||||
reason: false,
|
||||
file: false
|
||||
reason: false
|
||||
// file wurde entfernt da es optional ist
|
||||
};
|
||||
let isSubmitting = false;
|
||||
|
||||
@ -827,7 +827,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
|
||||
selectedFile = file;
|
||||
validationState.file = true;
|
||||
// validationState.file = true; // Entfernt, da Upload optional ist
|
||||
showFilePreview(file);
|
||||
updateFormValidation();
|
||||
|
||||
@ -913,7 +913,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
function removeFile() {
|
||||
selectedFile = null;
|
||||
validationState.file = false;
|
||||
// validationState.file = false; // Entfernt, da Upload optional ist
|
||||
|
||||
const previewContainer = document.getElementById('file-preview');
|
||||
const uploadContent = document.getElementById('upload-content');
|
||||
@ -1175,10 +1175,11 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!selectedFile) {
|
||||
showAdvancedMessage('Bitte wählen Sie eine Datei aus.', 'error');
|
||||
return;
|
||||
}
|
||||
// Datei ist jetzt optional - keine Validierung mehr erforderlich
|
||||
// if (!selectedFile) {
|
||||
// showAdvancedMessage('Bitte wählen Sie eine Datei aus.', 'error');
|
||||
// return;
|
||||
// }
|
||||
|
||||
isSubmitting = true;
|
||||
showLoadingState();
|
||||
@ -1190,7 +1191,11 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
formData.append('printer_id', document.getElementById('printer_id').value);
|
||||
formData.append('duration_min', document.getElementById('duration_min').value);
|
||||
formData.append('reason', document.getElementById('reason').value.trim());
|
||||
formData.append('file', selectedFile);
|
||||
|
||||
// Datei nur anhängen, wenn eine ausgewählt wurde
|
||||
if (selectedFile) {
|
||||
formData.append('file', selectedFile);
|
||||
}
|
||||
|
||||
// Add CSRF token
|
||||
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
|
||||
|
@ -177,6 +177,108 @@ def test_network_connectivity():
|
||||
except Exception as e:
|
||||
log_message(f"❌ Fehler beim Testen der Netzwerkverbindung: {str(e)}", "ERROR")
|
||||
|
||||
def test_tapo_connections():
|
||||
"""Teste TP-Link Tapo P110-Steckdosen-Verbindungen"""
|
||||
log_message("Teste TP-Link Tapo P110-Steckdosen-Verbindungen...")
|
||||
|
||||
try:
|
||||
# PyP100 importieren
|
||||
from PyP100 import PyP110
|
||||
log_message("✅ PyP100-Modul erfolgreich importiert")
|
||||
except ImportError:
|
||||
log_message("❌ PyP100-Modul nicht verfügbar", "ERROR")
|
||||
log_message(" Installiere mit: pip install PyP100", "INFO")
|
||||
return
|
||||
|
||||
# Lade Drucker aus Datenbank
|
||||
try:
|
||||
db_files = ['database.db', 'app.db', 'myp.db']
|
||||
printers = []
|
||||
|
||||
for db_file in db_files:
|
||||
if os.path.exists(db_file):
|
||||
conn = sqlite3.connect(db_file)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT id, name, plug_ip, plug_username, plug_password FROM printer WHERE plug_ip IS NOT NULL;")
|
||||
printers = cursor.fetchall()
|
||||
conn.close()
|
||||
break
|
||||
|
||||
if not printers:
|
||||
log_message("❌ Keine Drucker mit Tapo-Konfiguration gefunden")
|
||||
return
|
||||
|
||||
successful_connections = 0
|
||||
total_printers = len(printers)
|
||||
|
||||
for printer_id, name, plug_ip, plug_username, plug_password in printers:
|
||||
log_message(f"Teste Tapo-Verbindung zu {name} ({plug_ip})...")
|
||||
|
||||
# Konfiguration validieren
|
||||
if not all([plug_ip, plug_username, plug_password]):
|
||||
log_message(f" ❌ Unvollständige Konfiguration")
|
||||
missing = []
|
||||
if not plug_ip: missing.append("IP-Adresse")
|
||||
if not plug_username: missing.append("Benutzername")
|
||||
if not plug_password: missing.append("Passwort")
|
||||
log_message(f" Fehlend: {', '.join(missing)}")
|
||||
continue
|
||||
|
||||
try:
|
||||
# Tapo-Verbindung herstellen
|
||||
p110 = PyP110.P110(plug_ip, plug_username, plug_password)
|
||||
p110.handshake() # Authentifizierung
|
||||
p110.login() # Login
|
||||
|
||||
# Geräteinformationen abrufen
|
||||
device_info = p110.getDeviceInfo()
|
||||
|
||||
log_message(f" ✅ Tapo-Verbindung erfolgreich")
|
||||
log_message(f" 📛 Gerätename: {device_info.get('nickname', 'Unbekannt')}")
|
||||
log_message(f" ⚡ Status: {'Ein' if device_info.get('device_on', False) else 'Aus'}")
|
||||
|
||||
if 'on_time' in device_info:
|
||||
on_time = device_info.get('on_time', 0)
|
||||
hours, minutes = divmod(on_time // 60, 60)
|
||||
log_message(f" ⏱️ Betriebszeit: {hours}h {minutes}m")
|
||||
|
||||
if 'power_usage' in device_info:
|
||||
power_usage = device_info.get('power_usage', {})
|
||||
current_power = power_usage.get('power_mw', 0) / 1000 # mW zu W
|
||||
log_message(f" 🔋 Aktueller Verbrauch: {current_power:.1f}W")
|
||||
|
||||
successful_connections += 1
|
||||
|
||||
except Exception as e:
|
||||
log_message(f" ❌ Tapo-Verbindung fehlgeschlagen: {str(e)}")
|
||||
|
||||
# Detaillierte Fehleranalyse
|
||||
if "login" in str(e).lower():
|
||||
log_message(f" 🔐 Mögliche Ursache: Falsche Anmeldedaten")
|
||||
elif "timeout" in str(e).lower():
|
||||
log_message(f" ⏱️ Mögliche Ursache: Netzwerk-Timeout")
|
||||
elif "connect" in str(e).lower():
|
||||
log_message(f" 🌐 Mögliche Ursache: Steckdose nicht erreichbar")
|
||||
elif "handshake" in str(e).lower():
|
||||
log_message(f" 🤝 Mögliche Ursache: Protokoll-Handshake fehlgeschlagen")
|
||||
|
||||
# Zusammenfassung
|
||||
success_rate = (successful_connections / total_printers * 100) if total_printers > 0 else 0
|
||||
log_message(f"📊 Tapo-Verbindungs-Zusammenfassung:")
|
||||
log_message(f" Getestete Drucker: {total_printers}")
|
||||
log_message(f" Erfolgreiche Verbindungen: {successful_connections}")
|
||||
log_message(f" Erfolgsrate: {success_rate:.1f}%")
|
||||
|
||||
if successful_connections == total_printers:
|
||||
log_message("🎉 Alle Tapo-Verbindungen erfolgreich!")
|
||||
elif successful_connections > 0:
|
||||
log_message("⚠️ Einige Tapo-Verbindungen fehlgeschlagen")
|
||||
else:
|
||||
log_message("❌ Keine Tapo-Verbindungen erfolgreich", "ERROR")
|
||||
|
||||
except Exception as e:
|
||||
log_message(f"❌ Fehler beim Testen der Tapo-Verbindungen: {str(e)}", "ERROR")
|
||||
|
||||
def test_flask_app_status():
|
||||
"""Teste den Status der Flask-Anwendung"""
|
||||
log_message("Teste Flask-Anwendung...")
|
||||
@ -292,6 +394,10 @@ def run_comprehensive_test():
|
||||
test_network_connectivity()
|
||||
print()
|
||||
|
||||
# Tapo-Verbindungen testen
|
||||
test_tapo_connections()
|
||||
print()
|
||||
|
||||
log_message("=== Diagnose abgeschlossen ===")
|
||||
print()
|
||||
|
||||
|
@ -313,7 +313,14 @@ def toggle_plug(printer_id: int, state: bool) -> bool:
|
||||
db_session.close()
|
||||
return False
|
||||
|
||||
# Konfiguration validieren
|
||||
if not printer.plug_ip or not printer.plug_username or not printer.plug_password:
|
||||
logger.error(f"Unvollständige Tapo-Konfiguration für Drucker {printer.name} (ID: {printer_id})")
|
||||
db_session.close()
|
||||
return False
|
||||
|
||||
# TP-Link Tapo P110 Verbindung herstellen
|
||||
logger.debug(f"Verbinde zu Tapo-Steckdose {printer.plug_ip} für Drucker {printer.name}")
|
||||
p110 = PyP110.P110(printer.plug_ip, printer.plug_username, printer.plug_password)
|
||||
p110.handshake() # Authentifizierung
|
||||
p110.login() # Login
|
||||
@ -321,19 +328,60 @@ def toggle_plug(printer_id: int, state: bool) -> bool:
|
||||
# Steckdose ein-/ausschalten
|
||||
if state:
|
||||
p110.turnOn()
|
||||
logger.info(f"Steckdose für Drucker {printer.name} (ID: {printer_id}) eingeschaltet")
|
||||
logger.info(f"✅ Steckdose für Drucker {printer.name} (ID: {printer_id}) eingeschaltet")
|
||||
else:
|
||||
p110.turnOff()
|
||||
logger.info(f"Steckdose für Drucker {printer.name} (ID: {printer_id}) ausgeschaltet")
|
||||
logger.info(f"✅ Steckdose für Drucker {printer.name} (ID: {printer_id}) ausgeschaltet")
|
||||
|
||||
db_session.close()
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Schalten der Steckdose für Drucker {printer_id}: {str(e)}")
|
||||
logger.error(f"❌ Fehler beim Schalten der Steckdose für Drucker {printer_id}: {str(e)}")
|
||||
db_session.close()
|
||||
return False
|
||||
|
||||
|
||||
def test_tapo_connection(ip_address: str, username: str, password: str) -> dict:
|
||||
"""
|
||||
Testet die Verbindung zu einer Tapo-Steckdose und gibt detaillierte Informationen zurück.
|
||||
|
||||
Args:
|
||||
ip_address: IP-Adresse der Steckdose
|
||||
username: Benutzername
|
||||
password: Passwort
|
||||
|
||||
Returns:
|
||||
dict: Testergebnis mit Status und Informationen
|
||||
"""
|
||||
logger = get_logger("printers")
|
||||
result = {
|
||||
"success": False,
|
||||
"error": None,
|
||||
"device_info": None,
|
||||
"status": "unknown"
|
||||
}
|
||||
|
||||
try:
|
||||
logger.debug(f"Teste Tapo-Verbindung zu {ip_address}")
|
||||
p110 = PyP110.P110(ip_address, username, password)
|
||||
p110.handshake() # Authentifizierung
|
||||
p110.login() # Login
|
||||
|
||||
# Geräteinformationen abrufen
|
||||
device_info = p110.getDeviceInfo()
|
||||
result["device_info"] = device_info
|
||||
result["status"] = "on" if device_info.get('device_on', False) else "off"
|
||||
result["success"] = True
|
||||
|
||||
logger.debug(f"✅ Tapo-Verbindung zu {ip_address} erfolgreich")
|
||||
|
||||
except Exception as e:
|
||||
result["error"] = str(e)
|
||||
logger.warning(f"❌ Tapo-Verbindung zu {ip_address} fehlgeschlagen: {str(e)}")
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def check_jobs():
|
||||
"""
|
||||
Überprüft alle geplanten und laufenden Jobs und schaltet Steckdosen entsprechend.
|
||||
|
@ -13,11 +13,19 @@ from typing import Dict, Tuple, List, Optional
|
||||
from flask import session
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy.orm import Session
|
||||
import os
|
||||
|
||||
from models import get_db_session, Printer
|
||||
from utils.logging_config import get_logger
|
||||
from config.settings import PRINTERS
|
||||
|
||||
# TP-Link Tapo P110 Unterstützung hinzufügen
|
||||
try:
|
||||
from PyP100 import PyP110
|
||||
TAPO_AVAILABLE = True
|
||||
except ImportError:
|
||||
TAPO_AVAILABLE = False
|
||||
|
||||
# Logger initialisieren
|
||||
monitor_logger = get_logger("printer_monitor")
|
||||
|
||||
@ -110,57 +118,35 @@ class PrinterMonitor:
|
||||
|
||||
def _turn_outlet_off(self, ip_address: str, username: str, password: str, timeout: int = 5) -> bool:
|
||||
"""
|
||||
Schaltet eine Smart-Steckdose aus.
|
||||
Schaltet eine TP-Link Tapo P110-Steckdose aus.
|
||||
|
||||
Args:
|
||||
ip_address: IP-Adresse der Steckdose
|
||||
username: Benutzername für die Steckdose
|
||||
password: Passwort für die Steckdose
|
||||
timeout: Timeout in Sekunden
|
||||
timeout: Timeout in Sekunden (wird ignoriert, da PyP100 eigenes Timeout hat)
|
||||
|
||||
Returns:
|
||||
bool: True wenn erfolgreich ausgeschaltet
|
||||
"""
|
||||
if not TAPO_AVAILABLE:
|
||||
monitor_logger.error("⚠️ PyP100-Modul nicht verfügbar - kann Tapo-Steckdose nicht schalten")
|
||||
return False
|
||||
|
||||
try:
|
||||
# Für TP-Link Tapo und ähnliche Smart Plugs
|
||||
auth = (username, password)
|
||||
# TP-Link Tapo P110 Verbindung herstellen
|
||||
p110 = PyP110.P110(ip_address, username, password)
|
||||
p110.handshake() # Authentifizierung
|
||||
p110.login() # Login
|
||||
|
||||
# Verschiedene API-Endpunkte versuchen
|
||||
endpoints = [
|
||||
f"http://{ip_address}/relay/off",
|
||||
f"http://{ip_address}/api/relay/off",
|
||||
f"http://{ip_address}/set?relay=off",
|
||||
f"http://{ip_address}/control?action=off"
|
||||
]
|
||||
# Steckdose ausschalten
|
||||
p110.turnOff()
|
||||
monitor_logger.debug(f"✅ Tapo-Steckdose {ip_address} erfolgreich ausgeschaltet")
|
||||
return True
|
||||
|
||||
for endpoint in endpoints:
|
||||
try:
|
||||
response = requests.post(endpoint, auth=auth, timeout=timeout)
|
||||
if response.status_code in [200, 201, 204]:
|
||||
monitor_logger.debug(f"✅ Steckdose {ip_address} ausgeschaltet via {endpoint}")
|
||||
return True
|
||||
except requests.RequestException:
|
||||
continue
|
||||
|
||||
# Wenn spezifische Endpunkte fehlschlagen, versuche generische JSON-API
|
||||
try:
|
||||
payload = {"system": {"set_relay_state": {"state": 0}}}
|
||||
response = requests.post(
|
||||
f"http://{ip_address}/api",
|
||||
json=payload,
|
||||
auth=auth,
|
||||
timeout=timeout
|
||||
)
|
||||
if response.status_code in [200, 201]:
|
||||
monitor_logger.debug(f"✅ Steckdose {ip_address} ausgeschaltet via JSON-API")
|
||||
return True
|
||||
except requests.RequestException:
|
||||
pass
|
||||
|
||||
except Exception as e:
|
||||
monitor_logger.debug(f"⚠️ Fehler beim Ausschalten der Steckdose {ip_address}: {str(e)}")
|
||||
|
||||
return False
|
||||
monitor_logger.debug(f"⚠️ Fehler beim Ausschalten der Tapo-Steckdose {ip_address}: {str(e)}")
|
||||
return False
|
||||
|
||||
def get_live_printer_status(self, use_session_cache: bool = True) -> Dict[int, Dict]:
|
||||
"""
|
||||
@ -364,7 +350,6 @@ class PrinterMonitor:
|
||||
ipaddress.ip_address(ip_address.strip())
|
||||
|
||||
# Platform-spezifische Ping-Befehle
|
||||
import os
|
||||
if os.name == 'nt': # Windows
|
||||
cmd = ['ping', '-n', '1', '-w', str(timeout * 1000), ip_address.strip()]
|
||||
else: # Unix/Linux/macOS
|
||||
@ -386,79 +371,40 @@ class PrinterMonitor:
|
||||
|
||||
def _check_outlet_status(self, ip_address: str, username: str, password: str, timeout: int = 5) -> Tuple[bool, str]:
|
||||
"""
|
||||
Überprüft den Status einer Smart-Steckdose.
|
||||
Überprüft den Status einer TP-Link Tapo P110-Steckdose.
|
||||
|
||||
Args:
|
||||
ip_address: IP-Adresse der Steckdose
|
||||
username: Benutzername für die Steckdose
|
||||
password: Passwort für die Steckdose
|
||||
timeout: Timeout in Sekunden
|
||||
timeout: Timeout in Sekunden (wird ignoriert, da PyP100 eigenes Timeout hat)
|
||||
|
||||
Returns:
|
||||
Tuple[bool, str]: (Erreichbar, Status) - Status: "on", "off", "unknown"
|
||||
"""
|
||||
if not TAPO_AVAILABLE:
|
||||
monitor_logger.debug("⚠️ PyP100-Modul nicht verfügbar - kann Tapo-Steckdosen-Status nicht abfragen")
|
||||
return False, "unknown"
|
||||
|
||||
try:
|
||||
auth = (username, password)
|
||||
# TP-Link Tapo P110 Verbindung herstellen
|
||||
p110 = PyP110.P110(ip_address, username, password)
|
||||
p110.handshake() # Authentifizierung
|
||||
p110.login() # Login
|
||||
|
||||
# Verschiedene API-Endpunkte für Status-Abfrage versuchen
|
||||
status_endpoints = [
|
||||
f"http://{ip_address}/status",
|
||||
f"http://{ip_address}/api/status",
|
||||
f"http://{ip_address}/relay/status",
|
||||
f"http://{ip_address}/state"
|
||||
]
|
||||
# Geräteinformationen abrufen
|
||||
device_info = p110.getDeviceInfo()
|
||||
|
||||
for endpoint in status_endpoints:
|
||||
try:
|
||||
response = requests.get(endpoint, auth=auth, timeout=timeout)
|
||||
if response.status_code == 200:
|
||||
try:
|
||||
data = response.json()
|
||||
|
||||
# Verschiedene JSON-Strukturen handhaben
|
||||
if isinstance(data, dict):
|
||||
# TP-Link Format
|
||||
if 'system' in data and 'get_sysinfo' in data['system']:
|
||||
relay_state = data['system']['get_sysinfo'].get('relay_state')
|
||||
return True, "on" if relay_state == 1 else "off"
|
||||
|
||||
# Direktes Format
|
||||
if 'relay_state' in data:
|
||||
return True, "on" if data['relay_state'] in [1, True, "on"] else "off"
|
||||
|
||||
if 'state' in data:
|
||||
return True, "on" if data['state'] in [1, True, "on"] else "off"
|
||||
|
||||
if 'power' in data:
|
||||
return True, "on" if data['power'] in [1, True, "on"] else "off"
|
||||
|
||||
# Wenn JSON-Parsing funktioniert, aber Format unbekannt
|
||||
return True, "unknown"
|
||||
|
||||
except ValueError:
|
||||
# Nicht-JSON Antwort - versuche Text-Parsing
|
||||
text = response.text.lower()
|
||||
if "on" in text or "1" in text or "true" in text:
|
||||
return True, "on"
|
||||
elif "off" in text or "0" in text or "false" in text:
|
||||
return True, "off"
|
||||
return True, "unknown"
|
||||
|
||||
except requests.RequestException:
|
||||
continue
|
||||
# Status auswerten
|
||||
device_on = device_info.get('device_on', False)
|
||||
status = "on" if device_on else "off"
|
||||
|
||||
monitor_logger.debug(f"✅ Tapo-Steckdose {ip_address}: Status = {status}")
|
||||
return True, status
|
||||
|
||||
# Wenn spezifische Endpunkte fehlschlagen, einfache Konnektivitätsprüfung
|
||||
try:
|
||||
response = requests.get(f"http://{ip_address}", auth=auth, timeout=timeout)
|
||||
if response.status_code in [200, 401, 403]: # Erreichbar, aber möglicherweise Authentifizierungsproblem
|
||||
return True, "unknown"
|
||||
except requests.RequestException:
|
||||
pass
|
||||
|
||||
except Exception as e:
|
||||
monitor_logger.debug(f"⚠️ Fehler bei Steckdosen-Status-Check {ip_address}: {str(e)}")
|
||||
|
||||
return False, "unknown"
|
||||
monitor_logger.debug(f"⚠️ Fehler bei Tapo-Steckdosen-Status-Check {ip_address}: {str(e)}")
|
||||
return False, "unknown"
|
||||
|
||||
def clear_all_caches(self):
|
||||
"""Löscht alle Caches (Session und DB)."""
|
||||
|
Loading…
x
Reference in New Issue
Block a user