Commit d40e76
2026-06-06 00:00:22 Labor: Uploaded via inline attachment| /dev/null .. labor23/01_infrastruktur/hardware-inventar/geräteliste/labor23-terminal/bildschirm-dimmung/brightness-server.py | |
| @@ 0,0 1,128 @@ | |
| + | #!/usr/bin/env python3 |
| + | """ |
| + | Einfacher HTTP-Server zum Steuern der Bildschirmhelligkeit (und Farbtemperatur) |
| + | über wl-gammarelay-rs. |
| + | |
| + | Aufruf-Beispiele (z. B. aus Home Assistant per rest_command): |
| + | GET /set?b=0.5 -> Helligkeit auf 50 % |
| + | GET /set?b=0.3&t=4000 -> Helligkeit 30 %, Farbtemperatur 4000 K |
| + | GET /get -> aktuelle Helligkeit als Zahl (z. B. 0.5) |
| + | GET / -> kurze Hilfe / Status |
| + | |
| + | Laeuft als Benutzer 'kiosk' INNERHALB der labwc-Sitzung, damit 'busctl --user' |
| + | den Daemon direkt erreicht (XDG_RUNTIME_DIR ist dann korrekt gesetzt). |
| + | |
| + | Optional: einfacher Schutz per Token. Wenn unten TOKEN gesetzt ist, muss jede |
| + | Anfrage ?token=... mitschicken. Standard: aus (leer). |
| + | """ |
| + | |
| + | import os |
| + | import subprocess |
| + | from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer |
| + | from urllib.parse import urlparse, parse_qs |
| + | |
| + | # ----- Einstellungen (bei Bedarf anpassen) ----- |
| + | HOST = "0.0.0.0" # 0.0.0.0 = im ganzen LAN erreichbar |
| + | PORT = 8080 |
| + | TOKEN = "" # z. B. "geheim123" setzen, dann ?token=geheim123 noetig |
| + | DBUS_DEST = "rs.wl-gammarelay" |
| + | DBUS_PATH = "/" |
| + | DBUS_IFACE = "rs.wl.gammarelay" |
| + | |
| + | # Falls XDG_RUNTIME_DIR aus irgendeinem Grund fehlt, sinnvollen Default setzen |
| + | # (1001 ist die UID des kiosk-Users auf diesem Geraet). |
| + | os.environ.setdefault("XDG_RUNTIME_DIR", "/run/user/1001") |
| + | |
| + | |
| + | def busctl(*args): |
| + | """Ruft busctl --user auf und gibt (ok, ausgabe) zurueck.""" |
| + | cmd = ["busctl", "--user", *args] |
| + | try: |
| + | out = subprocess.run( |
| + | cmd, capture_output=True, text=True, timeout=5 |
| + | ) |
| + | if out.returncode != 0: |
| + | return False, (out.stderr or out.stdout).strip() |
| + | return True, out.stdout.strip() |
| + | except Exception as e: |
| + | return False, str(e) |
| + | |
| + | |
| + | def set_brightness(value): |
| + | value = max(0.0, min(1.0, float(value))) |
| + | return busctl("set-property", DBUS_DEST, DBUS_PATH, DBUS_IFACE, |
| + | "Brightness", "d", f"{value:.3f}") |
| + | |
| + | |
| + | def set_temperature(kelvin): |
| + | kelvin = int(max(1000, min(10000, int(kelvin)))) |
| + | return busctl("set-property", DBUS_DEST, DBUS_PATH, DBUS_IFACE, |
| + | "Temperature", "q", str(kelvin)) |
| + | |
| + | |
| + | def get_brightness(): |
| + | ok, out = busctl("get-property", DBUS_DEST, DBUS_PATH, DBUS_IFACE, |
| + | "Brightness") |
| + | if not ok: |
| + | return ok, out |
| + | # Ausgabe sieht aus wie: d 0.5 |
| + | parts = out.split() |
| + | return True, parts[-1] if parts else out |
| + | |
| + | |
| + | class Handler(BaseHTTPRequestHandler): |
| + | def _reply(self, code, text): |
| + | body = (text + "\n").encode() |
| + | self.send_response(code) |
| + | self.send_header("Content-Type", "text/plain; charset=utf-8") |
| + | self.send_header("Content-Length", str(len(body))) |
| + | self.end_headers() |
| + | self.wfile.write(body) |
| + | |
| + | def do_GET(self): |
| + | u = urlparse(self.path) |
| + | q = parse_qs(u.query) |
| + | |
| + | if TOKEN and q.get("token", [""])[0] != TOKEN: |
| + | return self._reply(403, "forbidden") |
| + | |
| + | if u.path == "/" or u.path == "/help": |
| + | return self._reply( |
| + | 200, |
| + | "brightness-server\n" |
| + | "/set?b=0.5 Helligkeit (0.0-1.0)\n" |
| + | "/set?b=0.3&t=4000 Helligkeit + Temperatur (K)\n" |
| + | "/get aktuelle Helligkeit", |
| + | ) |
| + | |
| + | if u.path == "/get": |
| + | ok, out = get_brightness() |
| + | return self._reply(200 if ok else 500, out) |
| + | |
| + | if u.path == "/set": |
| + | results = [] |
| + | if "b" in q: |
| + | ok, out = set_brightness(q["b"][0]) |
| + | results.append("brightness ok" if ok else f"brightness FEHLER: {out}") |
| + | if "t" in q: |
| + | ok, out = set_temperature(q["t"][0]) |
| + | results.append("temp ok" if ok else f"temp FEHLER: {out}") |
| + | if not results: |
| + | return self._reply(400, "fehlender Parameter b oder t") |
| + | failed = any("FEHLER" in r for r in results) |
| + | return self._reply(500 if failed else 200, "; ".join(results)) |
| + | |
| + | return self._reply(404, "unbekannter Pfad") |
| + | |
| + | # Logging in der Konsole knapp halten |
| + | def log_message(self, fmt, *args): |
| + | pass |
| + | |
| + | |
| + | if __name__ == "__main__": |
| + | srv = ThreadingHTTPServer((HOST, PORT), Handler) |
| + | print(f"brightness-server laeuft auf {HOST}:{PORT}") |
| + | try: |
| + | srv.serve_forever() |
| + | except KeyboardInterrupt: |
| + | pass |