233 lines
7.7 KiB
Python
233 lines
7.7 KiB
Python
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, get_wled_info
|
|
|
|
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 list of lines."""
|
|
if not csv_path or not os.path.exists(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 _extract_nr(value):
|
|
"""Extract bare number from ~~00341~~ -> 00341."""
|
|
return value.strip().strip("~")
|
|
|
|
|
|
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
|
|
|
|
nr = _extract_nr(personal_nr)
|
|
if not nr:
|
|
return None
|
|
|
|
# Find line containing this personal number
|
|
matching = [l for l in lines if nr in l]
|
|
if not matching:
|
|
return None
|
|
|
|
line = matching[0]
|
|
# Check status part (after comma or at end)
|
|
status_abwesend = _extract_nr(settings.get("aida_status_abwesend", "~~0~~"))
|
|
status_anwesend = _extract_nr(settings.get("aida_status_anwesend", "~~1~~"))
|
|
status_homeoffice = _extract_nr(settings.get("aida_status_homeoffice", "~~2~~"))
|
|
|
|
# Get the status value (last part after comma, stripped of ~)
|
|
parts = line.split(",")
|
|
status_val = _extract_nr(parts[-1]) if len(parts) > 1 else ""
|
|
|
|
if status_val == status_abwesend:
|
|
return "abwesend"
|
|
if status_val == status_anwesend:
|
|
return "anwesend"
|
|
if status_val == 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,
|
|
COALESCE(s.phone_active, 0) as phone_active
|
|
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
|
|
""").fetchall()
|
|
|
|
if not users_with_assignments:
|
|
logger.debug("Keine aktiven User mit Zuordnungen gefunden")
|
|
return
|
|
|
|
# Read AIDA CSV (kann leer sein)
|
|
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 ""
|
|
phone_active = user["phone_active"]
|
|
|
|
# Immer Teams-Status abfragen (fuer Dashboard-Anzeige)
|
|
teams_status = ""
|
|
presence = graph.get_presence(azure_id)
|
|
if presence and "activity" in presence:
|
|
teams_status = "teams_" + presence["activity"].lower()
|
|
|
|
# AIDA-Status ermitteln
|
|
aida_status = determine_aida_status(aida_lines, personal_nr, settings)
|
|
|
|
# Check if anything changed
|
|
if aida_status == last_aida and teams_status == last_teams:
|
|
return # No change
|
|
|
|
# --- WLED nur steuern wenn AIDA-Status bekannt und kein aktives Telefonat ---
|
|
wled_changed = aida_status is not None and not phone_active and (
|
|
aida_status != last_aida or
|
|
(aida_status == "anwesend" and teams_status != last_teams)
|
|
)
|
|
|
|
if wled_changed:
|
|
# Farbe bestimmen nach Originallogik:
|
|
# abwesend -> abwesend-Farbe
|
|
# homeoffice -> homeoffice-Farbe
|
|
# anwesend -> Teams-Farbe
|
|
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)
|
|
else:
|
|
color = get_color_for_status("available", conn)
|
|
|
|
url = build_wled_url(
|
|
hostname, color["r"], color["g"], color["b"],
|
|
segment=segment, effect=color.get("effect", "")
|
|
)
|
|
success, result_text = call_wled(url)
|
|
|
|
if not success:
|
|
logger.warning("WLED Fehler fuer %s: %s", display_name, result_text)
|
|
|
|
log_activity(
|
|
azure_id, display_name,
|
|
aida_status or "-", teams_status or "-",
|
|
url, result_text,
|
|
)
|
|
|
|
# State immer aktualisieren (fuer Dashboard)
|
|
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 or last_aida, teams_status, datetime.now(),
|
|
aida_status or last_aida, teams_status, datetime.now(),
|
|
))
|
|
conn.commit()
|
|
|
|
logger.info(
|
|
"%-40s aida=%-12s teams=%-20s wled=%s",
|
|
display_name, aida_status or "-", teams_status,
|
|
"aktualisiert" if wled_changed else "nur Dashboard",
|
|
)
|
|
|
|
|
|
def check_devices():
|
|
"""Check all WLED devices and store status in DB. Called periodically."""
|
|
conn = get_db()
|
|
try:
|
|
devices = conn.execute("SELECT id, hostname FROM wled_devices").fetchall()
|
|
for dev in devices:
|
|
info = get_wled_info(dev["hostname"])
|
|
if info.get("online"):
|
|
conn.execute("""
|
|
UPDATE wled_devices
|
|
SET online=1, leds=?, version=?, mac=?, last_check=?
|
|
WHERE id=?
|
|
""", (
|
|
info.get("leds", 0),
|
|
info.get("version", ""),
|
|
info.get("mac", ""),
|
|
datetime.now(),
|
|
dev["id"],
|
|
))
|
|
else:
|
|
conn.execute("""
|
|
UPDATE wled_devices SET online=0, last_check=? WHERE id=?
|
|
""", (datetime.now(), dev["id"]))
|
|
conn.commit()
|
|
logger.info("Device-Check: %d Geraete geprueft", len(devices))
|
|
except Exception as e:
|
|
logger.error("Device-Check Fehler: %s", e)
|
|
finally:
|
|
conn.close()
|