From c337a0d5f113a18c91c933672471a428fb64acc9 Mon Sep 17 00:00:00 2001 From: Mitja Horvat Date: Sat, 6 Jun 2026 15:54:28 +0200 Subject: [PATCH] First version -- working. --- off.sh | 38 ++++++++++ server.py | 204 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 242 insertions(+) create mode 100644 off.sh create mode 100755 server.py diff --git a/off.sh b/off.sh new file mode 100644 index 0000000..235dadc --- /dev/null +++ b/off.sh @@ -0,0 +1,38 @@ +TIME_START=00:00 +TIME_STOP=23:59 + +tv_check() +{ + ping -c 1 -W 1 192.168.1.12 2>&1 > /dev/null || { echo "TV is unreachable."; return; } + adb connect 192.168.1.12 + + if adb shell dumpsys power | grep mWakefulness=Awake + then + adb shell input keyevent 26 + else + echo "TV is turning OFF." + fi +} + +gabi_check() +{ + loginctl list-users | grep gabi || { echo 'Gabi is not online.'; return; } + doas loginctl terminate-user gabi +} + +S=$(date -d "$TIME_START" +%s) +T=$(date -d "$TIME_STOP" +%s) +C=$(date +%s) +if [ $C -lt $S ] +then + echo "PRE: $((S - C)) seconds to go." +elif [ $C -gt $T ] +then + echo "POST: $((C - T)) seconds passed." +else + echo "ACTIVE: Executing for the next $((T - C)) seconds." + gabi_check + tv_check +fi + +exit 0 diff --git a/server.py b/server.py new file mode 100755 index 0000000..21b3088 --- /dev/null +++ b/server.py @@ -0,0 +1,204 @@ +#!/usr/bin/env python3 +"""Simple web server: one button to turn off all kids' devices.""" + +import json +import re +import subprocess +from pathlib import Path +from http.server import HTTPServer, BaseHTTPRequestHandler + +SCRIPT = Path(__file__).parent / "off.sh" + + +def parse_output(text: str) -> list[dict]: + """Parse off.sh output into structured action items.""" + actions = [] + lines = text.strip().splitlines() + + for line in lines: + line = line.strip() + if not line: + continue + + # Time window messages + m = re.match(r"PRE:\s*(.+)seconds?\s*to\s*go\.", line) + if m: + actions.append({"icon": "⏳", "title": "Time Window", "detail": f"Before active hours β€” {m.group(1)}s until start"}) + continue + + m = re.match(r"POST:\s*(.+)seconds?\s*passed\.", line) + if m: + actions.append({"icon": "⏳", "title": "Time Window", "detail": f"After active hours β€” {m.group(1)}s past end"}) + continue + + m = re.match(r"ACTIVE:\s*(.+)", line) + if m: + actions.append({"icon": "🟒", "title": "Active Hours", "detail": m.group(1)}) + continue + + # TV checks + if "TV is unreachable" in line: + actions.append({"icon": "❌", "title": "TV", "detail": "Unreachable (ping failed)"}) + continue + + if "TV is turning OFF" in line: + actions.append({"icon": "πŸ“Ί", "title": "TV", "detail": "Already off β€” no action needed"}) + continue + + if "mWakefulness=Awake" in line: + actions.append({"icon": "πŸ“Ί", "title": "TV", "detail": "Screen turned off (power key sent)"}) + continue + + # User checks + if "Gabi is not online" in line: + actions.append({"icon": "πŸ‘€", "title": "Gabi's PC", "detail": "Not logged in β€” no action needed"}) + continue + + if "terminate-user gabi" in line or "terminated" in line.lower(): + actions.append({"icon": "πŸ’»", "title": "Gabi's PC", "detail": "Session terminated"}) + continue + + # If no structured items found, fallback to raw + if not actions: + actions.append({"icon": "πŸ“œ", "title": "Output", "detail": text.strip() or "(empty)"}) + + return actions + + +class Handler(BaseHTTPRequestHandler): + def do_GET(self): + if self.path == "/" or self.path == "/index.html": + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.end_headers() + self.wfile.write(HTML.encode()) + else: + self.send_error(404) + + def do_POST(self): + if self.path == "/run": + try: + result = subprocess.run( + ["bash", str(SCRIPT)], + capture_output=True, + text=True, + timeout=30, + ) + raw_output = (result.stdout + result.stderr).strip() or "Script executed (no output)." + status = "ok" if result.returncode == 0 else f"exit {result.returncode}" + except FileNotFoundError: + raw_output = f"Script not found at {SCRIPT}" + status = "error" + except subprocess.TimeoutExpired: + raw_output = "Script timed out after 30 seconds." + status = "error" + except Exception as e: + raw_output = str(e) + status = "error" + + actions = parse_output(raw_output) if status == "ok" else [{"icon": "❌", "title": "Error", "detail": raw_output}] + + self.send_response(200) + self.send_header("Content-Type", "application/json") + self.end_headers() + self.wfile.write(json.dumps({"status": status, "actions": actions}).encode()) + else: + self.send_error(404) + + def log_message(self, format, *args): + pass + + +HTML = """\ + + + + + + Kids Devices β€” OFF + + + +
+

πŸ“Ί Kids Devices

+

TV + Gabi's computer

+ +
+
+ + + +""" + +if __name__ == "__main__": + port = int(__import__("os").environ.get("PORT", "10000")) + server = HTTPServer(("0.0.0.0", port), Handler) + print(f"πŸš€ Open http://localhost:{port}") + server.serve_forever()