It appears that you've made several changes to your project's directory structure and files. Here's a summary of the actions:

This commit is contained in:
2025-06-19 09:50:15 +02:00
parent 44a80b7534
commit 52ff6b453e
122 changed files with 1337 additions and 826 deletions

View File

@@ -998,12 +998,36 @@ class TapoController:
# ===== PRINTER MONITOR =====
class PrinterMonitor:
"""3D-Drucker Monitor"""
"""3D-Drucker Monitor mit Status-Management und Session-Caching"""
# Status-Konstanten
STATUS_ON = "on"
STATUS_OFF = "off"
STATUS_UNREACHABLE = "unreachable"
# Status-Mapping für UI
STATUS_DISPLAY = {
STATUS_ON: {"text": "An", "color": "green", "icon": "power"},
STATUS_OFF: {"text": "Aus", "color": "gray", "icon": "power-off"},
STATUS_UNREACHABLE: {"text": "Nicht erreichbar", "color": "red", "icon": "exclamation-triangle"}
}
def __init__(self):
self.cache = {}
self._cache_timeout = 300 # 5 Minuten Cache
hardware_logger.info("✅ Printer Monitor initialisiert")
self._cache_lock = threading.RLock()
self._last_check = {}
self.check_interval = 30 # Sekunden zwischen Status-Checks
# Session-spezifischer Status-Cache für Benutzer-Sessions
self._session_cache = {}
self._session_cache_lock = threading.RLock()
self._session_cache_ttl = 300 # 5 Minuten für Session-Cache
# Thread-Pool für asynchrone Operationen
self._executor = ThreadPoolExecutor(max_workers=6)
hardware_logger.info("✅ Printer Monitor mit Session-Caching initialisiert")
def get_live_printer_status(self, use_session_cache: bool = True) -> Dict[int, Dict]:
"""
@@ -1148,8 +1172,343 @@ class PrinterMonitor:
def clear_all_caches(self):
"""Leert alle Caches des Printer Monitors."""
self.cache.clear()
hardware_logger.debug("Printer Monitor Cache geleert")
with self._cache_lock:
self.cache.clear()
self._last_check.clear()
with self._session_cache_lock:
self._session_cache.clear()
hardware_logger.debug("Alle Printer Monitor Caches geleert")
def control_plug(self, printer_id: int, action: str) -> Tuple[bool, str]:
"""
Steuert eine Tapo-Steckdose über den TapoController
Args:
printer_id: ID des Druckers
action: "on" oder "off"
Returns:
Tuple (Erfolg, Nachricht)
"""
try:
db_session = get_db_session()
printer = db_session.query(Printer).filter(Printer.id == printer_id).first()
if not printer:
return False, "Drucker nicht gefunden"
if not printer.plug_ip:
return False, "Keine Steckdose konfiguriert"
# Tapo-Controller verwenden
tapo_ctrl = get_tapo_controller()
if not tapo_ctrl:
return False, "Tapo-Controller nicht verfügbar"
# Aktion ausführen
success = False
if action == "on":
success = tapo_ctrl.toggle_plug(printer.plug_ip, True)
elif action == "off":
success = tapo_ctrl.turn_off(printer.plug_ip, printer_id=printer_id)
else:
return False, f"Ungültige Aktion: {action}"
if success:
# Cache invalidieren
with self._cache_lock:
if printer_id in self.cache:
del self.cache[printer_id]
if printer_id in self._last_check:
del self._last_check[printer_id]
db_session.close()
return True, f"Steckdose erfolgreich {action}"
else:
db_session.close()
return False, "Steckdose konnte nicht gesteuert werden"
except Exception as e:
hardware_logger.error(f"Fehler beim Steuern der Steckdose für Drucker {printer_id}: {str(e)}")
return False, str(e)
def check_and_control_for_jobs(self):
"""
Prüft alle Jobs und steuert Steckdosen entsprechend
Diese Methode sollte regelmäßig vom Scheduler aufgerufen werden
"""
try:
db_session = get_db_session()
now = datetime.now()
# Jobs die starten sollten
jobs_to_start = db_session.query(Job).filter(
Job.status == "scheduled",
Job.start_at <= now
).all()
for job in jobs_to_start:
hardware_logger.info(f"Starte Job {job.id} für Drucker {job.printer_id}")
success, msg = self.control_plug(job.printer_id, "on")
if success:
job.status = "running"
hardware_logger.info(f"Steckdose für Job {job.id} eingeschaltet")
else:
hardware_logger.error(f"Fehler beim Einschalten für Job {job.id}: {msg}")
# Jobs die enden sollten
jobs_to_end = db_session.query(Job).filter(
Job.status == "running",
Job.end_at <= now
).all()
for job in jobs_to_end:
hardware_logger.info(f"Beende Job {job.id} für Drucker {job.printer_id}")
success, msg = self.control_plug(job.printer_id, "off")
if success:
job.status = "finished"
job.actual_end_time = now
hardware_logger.info(f"Steckdose für Job {job.id} ausgeschaltet")
else:
hardware_logger.error(f"Fehler beim Ausschalten für Job {job.id}: {msg}")
db_session.commit()
db_session.close()
except Exception as e:
hardware_logger.error(f"Fehler bei der automatischen Job-Steuerung: {str(e)}")
def get_session_status(self, session_id: str, printer_ids: List[int] = None) -> Dict[str, Any]:
"""
Holt den gecachten Status für eine Session
Args:
session_id: Session-ID
printer_ids: Optional - spezifische Drucker-IDs
Returns:
Dict mit Session-spezifischen Status-Daten
"""
try:
with self._session_cache_lock:
session_data = self._session_cache.get(session_id, {})
# Prüfe Cache-Gültigkeit
cache_time = session_data.get('timestamp', datetime.min)
if (datetime.now() - cache_time).total_seconds() > self._session_cache_ttl:
# Cache abgelaufen
self._session_cache.pop(session_id, None)
return self._create_fresh_session_status(session_id, printer_ids)
# Wenn spezifische Drucker angefragt, filtere diese
if printer_ids:
filtered_status = {}
for printer_id in printer_ids:
if str(printer_id) in session_data.get('printers', {}):
filtered_status[str(printer_id)] = session_data['printers'][str(printer_id)]
return {
'timestamp': session_data['timestamp'],
'session_id': session_id,
'printers': filtered_status,
'from_cache': True
}
return session_data
except Exception as e:
hardware_logger.error(f"Fehler beim Abrufen des Session-Status: {str(e)}")
return self._create_fresh_session_status(session_id, printer_ids)
def update_session_status(self, session_id: str, printer_id: int = None) -> bool:
"""
Aktualisiert den Session-Status-Cache
Args:
session_id: Session-ID
printer_id: Optional - spezifischer Drucker
Returns:
bool: True wenn erfolgreich
"""
try:
with self._session_cache_lock:
if printer_id:
# Einzelnen Drucker aktualisieren
printer_status = self.get_live_printer_status(use_session_cache=False)
if session_id not in self._session_cache:
self._session_cache[session_id] = {
'timestamp': datetime.now(),
'session_id': session_id,
'printers': {}
}
self._session_cache[session_id]['printers'][str(printer_id)] = printer_status.get(printer_id, {})
self._session_cache[session_id]['timestamp'] = datetime.now()
else:
# Alle Drucker aktualisieren
self._session_cache[session_id] = self._create_fresh_session_status(session_id)
hardware_logger.debug(f"Session-Status für {session_id} aktualisiert")
return True
except Exception as e:
hardware_logger.error(f"Fehler beim Aktualisieren des Session-Status: {str(e)}")
return False
def clear_session_cache(self, session_id: str = None) -> bool:
"""
Löscht Session-Cache
Args:
session_id: Optional - spezifische Session, sonst alle
Returns:
bool: True wenn erfolgreich
"""
try:
with self._session_cache_lock:
if session_id:
self._session_cache.pop(session_id, None)
hardware_logger.debug(f"Session-Cache für {session_id} gelöscht")
else:
self._session_cache.clear()
hardware_logger.debug("Kompletter Session-Cache gelöscht")
return True
except Exception as e:
hardware_logger.error(f"Fehler beim Löschen des Session-Cache: {str(e)}")
return False
def _create_fresh_session_status(self, session_id: str, printer_ids: List[int] = None) -> Dict[str, Any]:
"""
Erstellt frischen Session-Status
Args:
session_id: Session-ID
printer_ids: Optional - spezifische Drucker-IDs
Returns:
Dict mit frischen Status-Daten
"""
try:
db_session = get_db_session()
# Alle oder spezifische Drucker laden
if printer_ids:
printers = db_session.query(Printer).filter(Printer.id.in_(printer_ids)).all()
else:
printers = db_session.query(Printer).all()
session_data = {
'timestamp': datetime.now(),
'session_id': session_id,
'printers': {},
'from_cache': False
}
# Status für jeden Drucker abrufen
status_dict = self.get_live_printer_status(use_session_cache=False)
for printer in printers:
session_data['printers'][str(printer.id)] = status_dict.get(printer.id, {})
# In Session-Cache speichern
with self._session_cache_lock:
self._session_cache[session_id] = session_data
db_session.close()
return session_data
except Exception as e:
hardware_logger.error(f"Fehler beim Erstellen frischen Session-Status: {str(e)}")
return {
'timestamp': datetime.now(),
'session_id': session_id,
'printers': {},
'error': str(e),
'from_cache': False
}
def invalidate_cache(self, printer_id: int = None) -> bool:
"""
Invalidiert Cache für spezifischen Drucker oder alle
Args:
printer_id: Optional - spezifischer Drucker, None = alle Drucker
Returns:
bool: True wenn erfolgreich
"""
try:
with self._cache_lock:
if printer_id is not None:
# Spezifischen Drucker-Cache löschen
self.cache.pop(printer_id, None)
self._last_check.pop(printer_id, None)
hardware_logger.debug(f"Cache für Drucker {printer_id} invalidiert")
else:
# Alle Caches löschen
self.cache.clear()
self._last_check.clear()
hardware_logger.info("Kompletter Status-Cache invalidiert")
return True
except Exception as e:
hardware_logger.error(f"Fehler beim Invalidieren des Cache: {str(e)}")
return False
def force_network_refresh(self) -> Dict[str, Any]:
"""
Forciert komplette Netzwerk-Neuprüfung aller Drucker
Invalidiert alle Caches und führt echte Netzwerk-Tests durch
Returns:
Dict mit Refresh-Ergebnissen
"""
try:
hardware_logger.info("Starte Force-Network-Refresh für alle Drucker")
# Alle Caches invalidieren
self.invalidate_cache()
self.clear_session_cache()
# Tapo-Controller Cache leeren
try:
tapo_ctrl = get_tapo_controller()
if tapo_ctrl and hasattr(tapo_ctrl, 'clear_cache'):
tapo_ctrl.clear_cache()
hardware_logger.debug("Tapo-Controller Cache geleert")
except Exception as e:
hardware_logger.warning(f"Tapo-Controller Cache konnte nicht geleert werden: {str(e)}")
# Frischen Status für alle Drucker abrufen
fresh_status = self.get_live_printer_status(use_session_cache=False)
# Ergebnisse zusammenfassen
results = {
"success": True,
"timestamp": datetime.now().isoformat(),
"printers_refreshed": len(fresh_status),
"printers": fresh_status,
"message": f"Netzwerk-Status für {len(fresh_status)} Drucker erfolgreich aktualisiert"
}
hardware_logger.info(f"Force-Network-Refresh abgeschlossen: {len(fresh_status)} Drucker aktualisiert")
return results
except Exception as e:
hardware_logger.error(f"Fehler beim Force-Network-Refresh: {str(e)}")
return {
"success": False,
"error": str(e),
"timestamp": datetime.now().isoformat(),
"message": "Fehler beim Aktualisieren der Netzwerk-Status"
}
# ===== GLOBALE INSTANZEN =====