diff --git a/sahsa-clock.service b/KH-clock.service similarity index 51% rename from sahsa-clock.service rename to KH-clock.service index 176cb4b..20ef6ae 100644 --- a/sahsa-clock.service +++ b/KH-clock.service @@ -1,24 +1,23 @@ [Unit] -Description=Sahsa Clock Display +Description=KH Clock Display After=network.target DefaultDependencies=no [Service] Type=simple User=pi -WorkingDirectory=/opt/sahsa_clock +WorkingDirectory=/opt/KH_Clock -# Tell SDL to render directly to the Linux framebuffer (no display server needed) -Environment=SDL_VIDEODRIVER=fbcon -Environment=SDL_FBDEV=/dev/fb0 +# Tell SDL to render via KMS/DRM (required on Le Potato and similar ARM boards) +Environment=SDL_VIDEODRIVER=kmsdrm # Disable console blanking so the TV stays on ExecStartPre=/bin/sh -c 'echo -ne "\033[9;0]" > /dev/tty1' # If using a virtualenv (recommended): -ExecStart=/opt/sahsa_clock/venv/bin/python3 /opt/sahsa_clock/main.py +ExecStart=/opt/KH_Clock/venv/bin/python3 /opt/KH_Clock/main.py # If using system Python instead, replace the line above with: -# ExecStart=/usr/bin/python3 /opt/sahsa_clock/main.py +# ExecStart=/usr/bin/python3 /opt/KH_Clock/main.py Restart=always RestartSec=5 diff --git a/PLAN.md b/PLAN.md index 6b532af..cf90d01 100644 --- a/PLAN.md +++ b/PLAN.md @@ -1,4 +1,4 @@ -# Plan: Sahsa Clock — Lightweight Boot-Persistent Display with Webhook Support +# Plan: KH Clock — Lightweight Boot-Persistent Display with Webhook Support ## Context @@ -27,7 +27,7 @@ Pygame can render directly to `/dev/fb0` (the Linux framebuffer) without any dis ## Architecture ``` -sahsa_clock/ +KH_Clock/ ├── main.py # Entry point: asyncio event loop + pygame render loop ├── display.py # Pygame rendering: clock face, message overlay ├── server.py # aiohttp server: dashboard routes + API routes @@ -37,7 +37,7 @@ sahsa_clock/ │ └── app.js ├── config.toml # Screen resolution, colors, fonts, port, token, timeout ├── requirements.txt -└── sahsa-clock.service # systemd unit file +└── KH-clock.service # systemd unit file ``` ### Runtime flow @@ -117,11 +117,11 @@ session_timeout_hours = 8 **No TLS required** — LAN-only. If ever internet-facing, add nginx in front with TLS. -### systemd service (`/etc/systemd/system/sahsa-clock.service`) +### systemd service (`/etc/systemd/system/KH-clock.service`) ```ini [Unit] -Description=Sahsa Clock Display +Description=KH Clock Display After=network.target DefaultDependencies=no @@ -130,7 +130,7 @@ Type=simple User=pi # or whatever the device user is Environment=SDL_VIDEODRIVER=fbcon Environment=SDL_FBDEV=/dev/fb0 -ExecStart=/usr/bin/python3 /opt/sahsa_clock/main.py +ExecStart=/usr/bin/python3 /opt/KH_Clock/main.py Restart=always RestartSec=5 @@ -259,4 +259,4 @@ The TV should show a plain text console at this point — that's expected. The c 5. Status bar in dashboard reflects the current message within 5 seconds 6. `curl -X POST http://:8080/api/message -H 'Authorization: Bearer ' -H 'Content-Type: application/json' -d '{"text":"API test", "duration": 30}'` — message appears 7. Same curl without the header → `401 Unauthorized` -8. `sudo systemctl enable --now sahsa-clock` → service starts, reboot device, clock appears without intervention +8. `sudo systemctl enable --now KH-clock` → service starts, reboot device, clock appears without intervention diff --git a/README.md b/README.md index d482fad..4bde74f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Sahsa Clock +# KH Clock A lightweight, boot-persistent clock display for a TV, running on a [Libre Computer AML-S905X-CC ("Le Potato")](https://libre.computer/products/aml-s905x-cc/). Renders directly to the Linux framebuffer — no desktop environment, no browser, no X11. Staff can push announcements to the screen from any phone or laptop on the same network. @@ -17,7 +17,7 @@ A lightweight, boot-persistent clock display for a TV, running on a [Libre Compu ## File structure ``` -sahsa_clock/ +KH_Clock/ ├── main.py # Entry point ├── display.py # Pygame rendering (clock + messages) ├── server.py # aiohttp web server (dashboard + API) @@ -29,7 +29,7 @@ sahsa_clock/ │ ├── style.css │ └── app.js ├── requirements.txt -└── sahsa-clock.service # systemd unit file +└── KH-clock.service # systemd unit file ``` --- @@ -276,14 +276,14 @@ ls -la /dev/fb0 # should exist, group should be 'video' ### 2. Install the application ```bash -sudo mkdir -p /opt/sahsa_clock -sudo chown : /opt/sahsa_clock +sudo mkdir -p /opt/KH_Clock +sudo chown : /opt/KH_Clock # Copy files (from your dev machine): -scp -r . @:/opt/sahsa_clock/ +scp -r . @:/opt/KH_Clock/ # On the Le Potato — create a venv and install dependencies: -cd /opt/sahsa_clock +cd /opt/KH_Clock python3 -m venv venv venv/bin/pip install -r requirements.txt ``` @@ -297,7 +297,7 @@ sudo apt install fonts-dejavu ### 4. Configure ```bash -nano /opt/sahsa_clock/config.toml +nano /opt/KH_Clock/config.toml ``` You only need to verify the `[server]` port and `[rate_limit]` values. Everything else is handled automatically on first run (token generation, password setup). @@ -305,21 +305,21 @@ You only need to verify the `[server]` port and `[rate_limit]` values. Everythin If the username on your device is not `pi`, open the service file and update the `User=` line: ```bash -nano /opt/sahsa_clock/sahsa-clock.service +nano /opt/KH_Clock/KH-clock.service ``` ### 5. Install and start the systemd service ```bash -sudo cp /opt/sahsa_clock/sahsa-clock.service /etc/systemd/system/ +sudo cp /opt/KH_Clock/KH-clock.service /etc/systemd/system/ sudo systemctl daemon-reload -sudo systemctl enable --now sahsa-clock +sudo systemctl enable --now KH-clock ``` **Check that it started:** ```bash -sudo systemctl status sahsa-clock +sudo systemctl status KH-clock ``` The TV should now show the clock. Open `http://:8080` from a laptop or phone on the same network to reach the dashboard. @@ -340,7 +340,7 @@ The TV should now show the clock. Open `http://:8080` from a laptop o **Clock doesn't appear after reboot:** ```bash -sudo journalctl -u sahsa-clock -n 50 +sudo journalctl -u KH-clock -n 50 ``` Common causes: wrong `User=` in the service file, user not in the `video` group, `/dev/fb0` not present. @@ -354,7 +354,7 @@ sudo usermod -aG video # add if missing, then log out and back in The desktop environment may still be running. Confirm `systemctl get-default` returns `multi-user.target` and that the display manager is disabled. **Dashboard unreachable:** -Check the service is running (`systemctl status sahsa-clock`) and that `port = 8080` in `config.toml` isn't blocked by a firewall. +Check the service is running (`systemctl status KH-clock`) and that `port = 8080` in `config.toml` isn't blocked by a firewall. **Font looks pixelated:** DejaVu fonts aren't installed. Run `sudo apt install fonts-dejavu` on the device and restart the service. diff --git a/config.py b/config.py index 7b7a53b..3c26105 100644 --- a/config.py +++ b/config.py @@ -52,7 +52,7 @@ class AppConfig: if not token: token = secrets.token_hex(32) _update_config_field(p, "token", token) - print(f"\n[sahsa-clock] Generated API bearer token (saved to config.toml):") + print(f"\n[KH-clock] Generated API bearer token (saved to config.toml):") print(f" {token}\n") return cls( diff --git a/dashboard/index.html b/dashboard/index.html index a60df37..06dac39 100644 --- a/dashboard/index.html +++ b/dashboard/index.html @@ -3,14 +3,14 @@ - Sahsa Clock + KH Clock
-

Sahsa Clock

+

KH Clock

diff --git a/display.py b/display.py index 5387356..9fb5b70 100644 --- a/display.py +++ b/display.py @@ -147,8 +147,7 @@ class DisplayThread(threading.Thread): if self.dev_mode: os.environ.setdefault("SDL_VIDEODRIVER", "x11") else: - os.environ["SDL_VIDEODRIVER"] = "fbcon" - os.environ["SDL_FBDEV"] = "/dev/fb0" + os.environ["SDL_VIDEODRIVER"] = "kmsdrm" try: pygame.init() @@ -216,7 +215,7 @@ class DisplayThread(threading.Thread): w = self.config.width or 1280 h = self.config.height or 720 surface = pygame.display.set_mode((w, h)) - pygame.display.set_caption("Sahsa Clock [DEV]") + pygame.display.set_caption("KH Clock [DEV]") return surface flags = pygame.FULLSCREEN | pygame.NOFRAME diff --git a/main.py b/main.py index 7cf6628..c02f302 100644 --- a/main.py +++ b/main.py @@ -8,7 +8,7 @@ from state import MessageState def main() -> None: - parser = argparse.ArgumentParser(description="Sahsa Clock Display") + parser = argparse.ArgumentParser(description="KH Clock Display") parser.add_argument( "--dev", action="store_true", @@ -39,7 +39,7 @@ def main() -> None: try: asyncio.run(run_server(state, config)) except KeyboardInterrupt: - print("\n[sahsa-clock] Shutting down.") + print("\n[KH-clock] Shutting down.") sys.exit(0) diff --git a/server.py b/server.py index 55e5b33..d49b8a4 100644 --- a/server.py +++ b/server.py @@ -14,7 +14,7 @@ from config import AppConfig from state import MessageState DASHBOARD_DIR = Path(__file__).parent / "dashboard" -SESSION_COOKIE = "sahsa_session" +SESSION_COOKIE = "kh_session" # ── Session store ───────────────────────────────────────────────────────────── @@ -116,12 +116,12 @@ def _login_page(error: str = "") -> str: - Sahsa Clock — Sign In + KH Clock — Sign In
-

Sahsa Clock

+

KH Clock

Sign in to access the dashboard.

{err}
@@ -141,7 +141,7 @@ def _setup_page(error: str = "") -> str: - Sahsa Clock — First Run Setup + KH Clock — First Run Setup @@ -396,6 +396,6 @@ async def run_server(state: MessageState, config: AppConfig) -> None: await runner.setup() site = web.TCPSite(runner, "0.0.0.0", config.port) await site.start() - print(f"[sahsa-clock] Dashboard: http://0.0.0.0:{config.port}") - print(f"[sahsa-clock] API: http://0.0.0.0:{config.port}/api/") + print(f"[KH-clock] Dashboard: http://0.0.0.0:{config.port}") + print(f"[KH-clock] API: http://0.0.0.0:{config.port}/api/") await asyncio.Event().wait() # run forever