"""Device management: check status and power off kids' devices.""" from datetime import datetime import subprocess def _run(cmd: list[str], *, timeout: int = 10) -> subprocess.CompletedProcess | None: """Run a command, return CompletedProcess or None on error.""" try: return subprocess.run(cmd, capture_output=True, text=True, timeout=timeout) except (FileNotFoundError, subprocess.TimeoutExpired): return None # ── TV ──────────────────────────────────────────────────────────────── def tv_check() -> dict: """Check if the TV is reachable and powered on. Returns an action dict describing the current state. """ result = _run(["ping", "-c", "1", "-W", "1", "192.168.1.12"]) if not result or result.returncode != 0: return {"icon": "❌", "title": "TV", "detail": "Unreachable (ping failed)"} _run(["adb", "connect", "192.168.1.12"]) result = _run(["adb", "shell", "dumpsys", "power"]) if result and "mWakefulness=Awake" in result.stdout: return {"icon": "📺", "title": "TV", "detail": "Screen is on"} return {"icon": "📺", "title": "TV", "detail": "Already off"} def tv_turnoff() -> dict | None: """Send power key to the TV if it's on. Returns action dict or None.""" state = tv_check() if state["detail"] != "Screen is on": return None _run(["adb", "shell", "input", "keyevent", "26"]) return {"icon": "📺", "title": "TV", "detail": "Screen turned off (power key sent)"} # ── Gabi ────────────────────────────────────────────────────────────── def gabi_check() -> bool: """Return True if user 'gabi' has an active login session.""" result = _run(["loginctl", "list-users"]) return result is not None and "gabi" in result.stdout def gabi_turnoff() -> dict | None: """Log out Gabi if she's logged in. Returns action dict or None.""" if not gabi_check(): return {"icon": "💻", "title": "Gabi's PC", "detail": "Not logged in — no action needed"} result = _run(["doas", "loginctl", "terminate-user", "gabi"]) if result and result.returncode == 0: return {"icon": "💻", "title": "Gabi's PC", "detail": "Session terminated"} return {"icon": "❌", "title": "Gabi's PC", "detail": "Failed to terminate session"} # ── Gaja ────────────────────────────────────────────────────────────── def gaja_check() -> bool: """Return True if user 'gaja' has an active login session on her PC.""" result = _run(["ping", "-c", "1", "-W", "1", "192.168.1.122"]) if not result or result.returncode != 0: return False check = _run( ["sshpass", "-p", "Nagaja", "ssh", "-o", "ConnectTimeout=5", "-o", "StrictHostKeyChecking=no", "-F", "/dev/null", "gaja@192.168.1.122", "loginctl list-sessions --no-pager 2>/dev/null | grep -q gaja"] ) return check is not None and check.returncode == 0 def gaja_turnoff() -> dict | None: """Log out Gaja if she's logged in. Returns action dict or None.""" # Ping check result = _run(["ping", "-c", "1", "-W", "1", "192.168.1.122"]) if not result or result.returncode != 0: return {"icon": "❌", "title": "Gaja's PC", "detail": "Unreachable (ping failed)"} if not gaja_check(): return {"icon": "💻", "title": "Gaja's PC", "detail": "Not logged in — no action needed"} _run( ["sshpass", "-p", "Nagaja", "ssh", "-o", "ConnectTimeout=5", "-o", "StrictHostKeyChecking=no", "-F", "/dev/null", "gaja@192.168.1.122", "loginctl terminate-user gaja"], timeout=5 ) return {"icon": "💻", "title": "Gaja's PC", "detail": "Logged out"} # ── Orchestrator ────────────────────────────────────────────────────── def shutdown_all() -> list[dict]: """Run all device checks and logouts. Returns ordered list of action dicts.""" actions = [] # Time window now = datetime.now() time_start = now.replace(hour=0, minute=0, second=0) time_stop = now.replace(hour=23, minute=59, second=59) if now < time_start: diff = int((time_start - now).total_seconds()) actions.append({"icon": "⏳", "title": "Time Window", "detail": f"Before active hours — {diff}s until start"}) elif now > time_stop: diff = int((now - time_stop).total_seconds()) actions.append({"icon": "⏳", "title": "Time Window", "detail": f"After active hours — {diff}s past end"}) else: actions.append({"icon": "🟢", "title": "Active Hours", "detail": f"Executing for the next {time_stop - now}"}) # Check → act for each device for check_fn, action_fn in [ (tv_check, tv_turnoff), (gabi_check, gabi_turnoff), (gaja_check, gaja_turnoff), ]: status = check_fn() if isinstance(status, dict): actions.append(status) # e.g. unreachable elif status is True: result = action_fn() if result: actions.append(result) return actions