import logging import os from datetime import datetime from database import get_db, get_setting, log_activity from wled_client import build_wled_url, call_wled logger = logging.getLogger(__name__) # Global reference to graph client (set by app.py on startup) graph = None def read_aida_csv(csv_path): """Read AIDA status CSV. Returns dict: personal_nr -> line content.""" if not csv_path or not os.path.exists(csv_path): logger.warning("AIDA CSV nicht gefunden: %s", csv_path) return {} try: with open(csv_path, "r", encoding="utf-8", errors="replace") as f: lines = f.read().splitlines() return lines except Exception as e: logger.error("Fehler beim Lesen der AIDA CSV: %s", e) return [] def determine_aida_status(lines, personal_nr, settings): """Determine AIDA status for a user based on their personal number.""" if not personal_nr or not lines: return None matching = [l for l in lines if l.startswith(personal_nr.lower())] if not matching: return None line = matching[0] if line.endswith(settings["aida_status_abwesend"]): return "abwesend" if line.endswith(settings["aida_status_anwesend"]): return "anwesend" if line.endswith(settings["aida_status_homeoffice"]): return "homeoffice" return None def get_color_for_status(status, conn): """Get RGB color for a given status from color_rules.""" row = conn.execute( "SELECT r, g, b, effect FROM color_rules WHERE status = ?", (status,) ).fetchone() if row: return dict(row) return {"r": 200, "g": 200, "b": 200, "effect": ""} def poll_status(): """Main polling function - called periodically by APScheduler.""" global graph if graph is None or not graph.is_authenticated(): logger.warning("Graph-Client nicht authentifiziert, ueberspringe Polling") return conn = get_db() try: settings = {} for row in conn.execute("SELECT key, value FROM settings").fetchall(): settings[row["key"]] = row["value"] # Get all active users with assignments users_with_assignments = conn.execute(""" SELECT u.azure_id, u.display_name, u.personal_nr, a.segment, d.hostname, s.last_aida_status, s.last_teams_status FROM users u JOIN assignments a ON a.user_azure_id = u.azure_id JOIN wled_devices d ON d.id = a.wled_device_id LEFT JOIN state s ON s.user_azure_id = u.azure_id WHERE u.active = 1 AND u.personal_nr != '' """).fetchall() if not users_with_assignments: logger.debug("Keine aktiven User mit Zuordnungen gefunden") return # Read AIDA CSV aida_lines = read_aida_csv(settings.get("aida_csv_path", "")) for user in users_with_assignments: try: _process_user(user, aida_lines, settings, conn) except Exception as e: logger.error( "Fehler bei User %s: %s", user["display_name"], e ) except Exception as e: logger.error("Polling Fehler: %s", e) finally: conn.close() def _process_user(user, aida_lines, settings, conn): """Process a single user: check status and update WLED if needed.""" global graph azure_id = user["azure_id"] display_name = user["display_name"] personal_nr = user["personal_nr"] hostname = user["hostname"] segment = user["segment"] last_aida = user["last_aida_status"] or "" last_teams = user["last_teams_status"] or "" # Determine AIDA status aida_status = determine_aida_status(aida_lines, personal_nr, settings) if aida_status is None: return # Determine Teams status teams_status = "" if aida_status == "anwesend": presence = graph.get_presence(azure_id) if presence and "activity" in presence: teams_status = "teams_" + presence["activity"].lower() # Check if status changed if aida_status == last_aida: if aida_status == "anwesend": if teams_status == last_teams: return # No change else: return # No change # Determine color if aida_status == "abwesend": color = get_color_for_status("abwesend", conn) elif aida_status == "homeoffice": color = get_color_for_status("homeoffice", conn) elif aida_status == "anwesend" and teams_status: color = get_color_for_status(teams_status, conn) if not color: color = get_color_for_status("teams_available", conn) else: color = get_color_for_status("available", conn) # Build URL and call WLED url = build_wled_url( hostname, color["r"], color["g"], color["b"], segment=segment, effect=color.get("effect", "") ) success, result_text = call_wled(url) if success: # Update state conn.execute(""" INSERT INTO state (user_azure_id, last_aida_status, last_teams_status, updated_at) VALUES (?, ?, ?, ?) ON CONFLICT(user_azure_id) DO UPDATE SET last_aida_status=?, last_teams_status=?, updated_at=? """, ( azure_id, aida_status, teams_status, datetime.now(), aida_status, teams_status, datetime.now(), )) conn.commit() logger.info( "%-40s aida=%-12s teams=%-20s -> %s", display_name, aida_status, teams_status, result_text, ) else: # Reset state on failure conn.execute(""" INSERT INTO state (user_azure_id, last_aida_status, last_teams_status, updated_at) VALUES (?, '', '', ?) ON CONFLICT(user_azure_id) DO UPDATE SET last_aida_status='', last_teams_status='', updated_at=? """, (azure_id, datetime.now(), datetime.now())) conn.commit() # Log activity log_activity( azure_id, display_name, aida_status, teams_status, url, result_text )