From de15ee2e636d7308d6e3b4383227be99cecfdff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesko=20Ansch=C3=BCtz?= Date: Sun, 22 Mar 2026 12:46:34 +0100 Subject: [PATCH] Lege Planungsgrundlage fuer neues Info-Board an --- API-MQTT-VERTRAG.md | 424 ++++++++++++++++++++++++++++++++++++++++++++ DATENMODELL.md | 391 ++++++++++++++++++++++++++++++++++++++++ PLAN.md | 416 +++++++++++++++++++++++++++++++++++++++++++ README.md | 24 +++ TODO.md | 132 ++++++++++++++ 5 files changed, 1387 insertions(+) create mode 100644 API-MQTT-VERTRAG.md create mode 100644 DATENMODELL.md create mode 100644 PLAN.md create mode 100644 README.md create mode 100644 TODO.md diff --git a/API-MQTT-VERTRAG.md b/API-MQTT-VERTRAG.md new file mode 100644 index 0000000..6f01e7d --- /dev/null +++ b/API-MQTT-VERTRAG.md @@ -0,0 +1,424 @@ +# Info-Board Neu - API- und MQTT-Vertrag + +## Ziel + +Dieses Dokument beschreibt die technische Arbeitsteilung zwischen HTTPS-API und MQTT. + +Grundregel: + +- HTTPS fuer Daten, Uploads, Konfiguration und Authentifizierung +- MQTT fuer Ereignisse, Heartbeats, Status und Fernsteuerung + +## Architekturprinzip + +Der Player arbeitet autonom aus lokalem Zustand. + +Das bedeutet: + +- die API liefert Konfiguration, Playlist und Medieninformationen +- der Player cached diese Daten lokal +- MQTT informiert ueber Aenderungen und traegt leichte Steuerkommandos +- bei Ausfall der Verbindung laeuft der Player mit dem letzten gueltigen Stand weiter + +## HTTPS-API + +### Authentifizierung + +Die Web-Oberflaechen nutzen benutzerbezogene Sessions oder Tokens. + +Die Player nutzen ein geraetebezogenes Token. + +Empfohlene Aufteilung: + +- Browser-UI: Cookie-Session oder JWT +- Player: statischer oder rotierbarer API-Token je Screen + +## API-Bereiche + +### 1. Auth + +Zweck: + +- Login +- Logout +- Session-Pruefung + +Beispiel-Endpunkte: + +- `POST /api/v1/auth/login` +- `POST /api/v1/auth/logout` +- `GET /api/v1/auth/me` + +### 2. Tenants und Benutzer + +Zweck: + +- Verwaltung von Firmen und Benutzern + +Beispiel-Endpunkte: + +- `GET /api/v1/tenants` +- `POST /api/v1/tenants` +- `GET /api/v1/tenants/:tenantId/users` +- `POST /api/v1/tenants/:tenantId/users` + +### 3. Screens + +Zweck: + +- technische und fachliche Verwaltung der Monitore + +Beispiel-Endpunkte: + +- `GET /api/v1/screens` +- `GET /api/v1/screens/:screenId` +- `PATCH /api/v1/screens/:screenId` +- `GET /api/v1/screens/:screenId/status` +- `GET /api/v1/screens/:screenId/snapshots/latest` + +### 4. Medien + +Zweck: + +- Uploads +- Listenansicht +- Loeschen +- Metadatenbearbeitung + +Beispiel-Endpunkte: + +- `GET /api/v1/media` +- `POST /api/v1/media/upload` +- `POST /api/v1/media/import-url` +- `PATCH /api/v1/media/:mediaId` +- `DELETE /api/v1/media/:mediaId` + +### 5. Playlists + +Zweck: + +- Playlist lesen und bearbeiten +- Reihenfolge und Zeitfenster verwalten + +Beispiel-Endpunkte: + +- `GET /api/v1/screens/:screenId/playlist` +- `PUT /api/v1/screens/:screenId/playlist` +- `POST /api/v1/screens/:screenId/playlist/items` +- `PATCH /api/v1/screens/:screenId/playlist/items/:itemId` +- `DELETE /api/v1/screens/:screenId/playlist/items/:itemId` + +### 6. Player-Sync + +Zweck: + +- geraeteseitiger Abruf von Konfiguration und Daten +- differenzieller Sync ueber Revisionen + +Beispiel-Endpunkte: + +- `POST /api/v1/player/register` +- `GET /api/v1/player/config` +- `GET /api/v1/player/playlist` +- `GET /api/v1/player/media-manifest` +- `GET /api/v1/player/media/:mediaId/download` +- `POST /api/v1/player/status` +- `POST /api/v1/player/snapshot` + +### 7. Admin-Kommandos + +Zweck: + +- Ausloesen von Fernaktionen + +Beispiel-Endpunkte: + +- `POST /api/v1/screens/:screenId/commands/reload` +- `POST /api/v1/screens/:screenId/commands/restart-player` +- `POST /api/v1/screens/:screenId/commands/reboot` +- `POST /api/v1/screens/:screenId/commands/display-on` +- `POST /api/v1/screens/:screenId/commands/display-off` + +## API-Antworten fuer den Player + +### `GET /api/v1/player/config` + +Liefert: + +- `screen_id` +- `screen_slug` +- `config_revision` +- `player_settings` +- `fallback_dir` +- `snapshot_interval_seconds` +- `offline_overlay_enabled` +- `rotation` + +### `GET /api/v1/player/playlist` + +Liefert: + +- `playlist_id` +- `playlist_revision` +- `default_duration_seconds` +- `fallback_enabled` +- `items[]` + +Jedes Item enthaelt mindestens: + +- `id` +- `type` +- `src` +- `duration_seconds` +- `load_timeout_seconds` +- `cache_policy` +- `on_error` +- `retry_count` +- `valid_from` +- `valid_until` +- `enabled` + +### `GET /api/v1/player/media-manifest` + +Liefert: + +- `media_revision` +- `assets[]` + +Jeder Asset-Eintrag enthaelt mindestens: + +- `id` +- `type` +- `source_kind` +- `download_url` nullable +- `original_url` nullable +- `checksum` +- `size_bytes` +- `mime_type` +- `cache_required` + +## MQTT-Verwendung + +MQTT dient nur der schnellen, leichten Signal- und Statuskommunikation. + +Nicht ueber MQTT transportieren: + +- Datei-Uploads +- grosse JSON-Payloads fuer komplette Medienlisten +- eigentliche Mediendateien + +## MQTT-Topics + +### Heartbeat + +- `signage/screen//heartbeat` + +Payload-Beispiel: + +```json +{ + "screen_id": "info01", + "ts": "2026-03-22T12:00:00Z", + "online": true, + "server_connected": true, + "mqtt_connected": true, + "player_version": "0.1.0", + "uptime_seconds": 86400 +} +``` + +### Status + +- `signage/screen//status` + +Payload-Beispiel: + +```json +{ + "screen_id": "info01", + "ts": "2026-03-22T12:00:00Z", + "current_item_id": "pli_123", + "current_item_type": "image", + "current_item_label": "Begruessung", + "current_item_started_at": "2026-03-22T11:59:45Z", + "current_item_duration_seconds": 20, + "overlay_state": "online", + "cache_state": "ok", + "error_code": null, + "error_message": null +} +``` + +### Events + +- `signage/screen//event` + +Typische Events: + +- `playlist_changed` +- `sync_completed` +- `sync_failed` +- `item_failed` +- `browser_restarted` +- `snapshot_uploaded` + +### Commands + +- `signage/screen//command` + +Payload-Beispiel: + +```json +{ + "command_id": "cmd_456", + "type": "reload", + "payload": {}, + "requested_at": "2026-03-22T12:00:00Z" +} +``` + +### ACK + +- `signage/screen//ack` + +Payload-Beispiel: + +```json +{ + "command_id": "cmd_456", + "screen_id": "info01", + "ts": "2026-03-22T12:00:02Z", + "result": "ok", + "message": "playlist reloaded" +} +``` + +## QoS- und Broker-Hinweise + +Empfehlung: + +- Heartbeat: QoS 0 oder 1 +- Status: QoS 0 oder 1 +- Commands: QoS 1 +- ACK: QoS 1 + +Zusatzempfehlungen: + +- Last-Will fuer Offline-Erkennung nutzen +- retained Messages nur sparsam und gezielt einsetzen +- kein Missbrauch von retained Commands + +## Trennung von Pull und Push + +### Pull ueber HTTPS + +Der Player holt aktiv: + +- Konfiguration +- Playlist +- Medienmanifest +- eigentliche Medien + +### Push ueber MQTT + +Der Server signalisiert aktiv: + +- es gibt neue Konfiguration +- es gibt neue Playlist-Daten +- fuehre einen Befehl aus + +Beispielablauf bei Playlist-Aenderung: + +1. Benutzer speichert Playlist in der Web-UI +2. Server erhoeht `playlist_revision` +3. Server sendet Event oder Command `reload` +4. Player ruft `GET /api/v1/player/playlist` ab +5. Player synchronisiert fehlende Medien +6. Player bestaetigt erfolgreichen Reload + +## Offline-Verhalten + +### Wenn HTTPS ausfaellt + +- Player nutzt letzte lokale Konfiguration +- Player nutzt letzte lokale Playlist +- Player nutzt lokalen Mediencache +- Overlay signalisiert Offline-Zustand + +### Wenn MQTT ausfaellt + +- Player laeuft normal weiter +- periodischer HTTPS-Sync erkennt spaetere Aenderungen trotzdem +- Admin-Kommandos werden ggf. verzoegert, aber nicht die Anzeige selbst + +### Wenn beides ausfaellt + +- Player bleibt autonom betriebsfaehig +- keine frischen Inhalte oder Befehle mehr +- bestehende Playlist/Fallback laufen weiter + +## Web-Inhalte und Fehlerstrategie + +Webseiten sind der stoeranfaelligste Medientyp. + +Regeln: + +- Chromium zeigt immer nur die lokale `player-ui` +- `player-ui` bettet Web-Inhalte kontrolliert ein +- jedes Web-Item hat einen `load_timeout_seconds` +- bei Fehler gilt `on_error` + +Typische Fehlerpfade: + +- 404 oder DNS-Problem -> `skip` oder `fallback` +- langsamer Ladevorgang -> Timeout -> `retry` oder `skip` +- Renderer-Fehler -> lokaler Watchdog startet den Renderer neu + +## Screenshot-Upload + +Der Player erstellt Screenshots: + +- bei Item-Wechsel +- zyklisch +- auf Admin-Anforderung + +Ablauf: + +1. Player erzeugt Bild lokal +2. Player sendet es per HTTPS an `POST /api/v1/player/snapshot` +3. Server speichert das Bild und aktualisiert die letzte Vorschau des Screens + +## Sicherheitsgrundsaetze + +- jeder Player hat eine eigene technische Identitaet +- API und MQTT werden authentifiziert +- Tenant-Benutzer sehen nur tenantbezogene Objekte +- Admin-Kommandos werden serverseitig protokolliert +- Screenshots und Statusdaten sind nur fuer berechtigte Nutzer sichtbar + +## Minimale API-Frequenzen + +Sinnvolle Startwerte: + +- Heartbeat alle 30 Sekunden +- Status-Update bei Item-Wechsel und zusaetzlich alle 60 Sekunden +- Konfigurations-/Playlist-Poll als Fallback alle 5 Minuten +- Snapshot alle 60 Sekunden oder bei Item-Wechsel + +## V1-Abgrenzung + +Fuer Version 1 bewusst nicht zwingend enthalten: + +- Live-Video-Stream der Monitore +- komplexe Kampagnenlogik mit Prioritaeten +- Multi-Screen-Synchronisation auf Frame-Ebene +- serverseitiges Video-Transcoding + +## Empfohlene naechste Dokumente + +Als direkte Folge sollten entstehen: + +- technisches Player-Konzept +- Server-Komponentenplan +- Ansible-Deploymentplan +- Rechte- und Authentifizierungskonzept diff --git a/DATENMODELL.md b/DATENMODELL.md new file mode 100644 index 0000000..4d09c96 --- /dev/null +++ b/DATENMODELL.md @@ -0,0 +1,391 @@ +# Info-Board Neu - Datenmodell + +## Ziel + +Das Datenmodell trennt klar zwischen: + +- Mandanten und Benutzern +- Bildschirmen und ihrer technischen Laufzeitinformation +- Medien und Playlists +- Steuerbefehlen und Rueckmeldungen + +Das Modell ist so ausgelegt, dass: + +- jede Firma nur ihren eigenen Monitor bzw. Kanal pflegt +- die Administration alle Monitore zentral sehen und steuern kann +- Offline-Betrieb der Player moeglich bleibt +- Zeitsteuerung mit `valid_from` und `valid_until` sauber abbildbar ist + +## Kernentitaeten + +### `tenant` + +Repraesentiert eine Firma bzw. einen abgeschotteten Inhaltsbereich. + +Felder: + +- `id` +- `slug` +- `name` +- `active` +- `created_at` +- `updated_at` + +Hinweise: + +- ein Tenant kann spaeter mehrere Screens haben, auch wenn zunaechst meist 1:1 gearbeitet wird +- alle Medien und Playlists sind einem Tenant zugeordnet + +### `user` + +Repraesentiert einen Benutzer der Firmen- oder Admin-Oberflaeche. + +Felder: + +- `id` +- `tenant_id` nullable fuer globale Admins +- `username` +- `email` +- `password_hash` +- `role` (`admin`, `tenant_user`) +- `active` +- `last_login_at` +- `created_at` +- `updated_at` + +Regeln: + +- `tenant_user` darf nur Daten des eigenen Tenants sehen und bearbeiten +- `admin` darf alle Tenants und alle Screens verwalten + +### `screen` + +Repraesentiert einen physischen Monitor bzw. einen Player-Client. + +Felder: + +- `id` +- `tenant_id` +- `slug` +- `name` +- `description` +- `location` +- `hardware_name` +- `enabled` +- `rotation` (`0`, `90`, `180`, `270`) +- `resolution_width` +- `resolution_height` +- `fallback_dir` +- `snapshot_interval_seconds` +- `offline_overlay_enabled` +- `created_at` +- `updated_at` + +Hinweise: + +- der `slug` dient als technische Kennung fuer Konfiguration, API und MQTT +- `fallback_dir` beschreibt die lokale oder synchronisierte Fallback-Quelle auf dem Client + +### `screen_registration` + +Optionale Trennung zwischen fachlichem Screen und technischer Registrierung. + +Felder: + +- `id` +- `screen_id` +- `device_uuid` +- `hostname` +- `api_token_hash` +- `mqtt_client_id` +- `last_seen_at` +- `last_ip` +- `player_version` +- `os_version` +- `created_at` +- `updated_at` + +Zweck: + +- Wiedererkennung eines konkreten Geraets +- sichere Kommunikation mit API und Broker +- technische Inventarisierung + +### `media_asset` + +Repraesentiert ein verwaltetes Medium. + +Felder: + +- `id` +- `tenant_id` +- `screen_id` nullable +- `title` +- `description` +- `type` (`image`, `video`, `pdf`, `web`) +- `source_kind` (`upload`, `remote_url`) +- `storage_path` nullable bei reinen Web-URLs +- `original_url` nullable +- `mime_type` +- `checksum` +- `size_bytes` +- `enabled` +- `created_by_user_id` +- `created_at` +- `updated_at` + +Regeln: + +- `upload` bedeutet: Datei liegt im Medien-Storage +- `remote_url` bedeutet: Quelle ist extern und wird vom Player oder Server gecacht +- `screen_id` kann optional gesetzt werden, wenn ein Medium nur fuer einen Monitor gedacht ist + +### `playlist` + +Repraesentiert eine logische Playlist eines Screens. + +Felder: + +- `id` +- `tenant_id` +- `screen_id` +- `name` +- `is_active` +- `default_duration_seconds` +- `fallback_enabled` +- `fallback_dir` +- `shuffle_enabled` +- `created_at` +- `updated_at` + +Regeln: + +- pro Screen gibt es in v1 genau eine aktive Playlist +- spaeter koennen mehrere Playlists mit Umschaltung moeglich werden + +### `playlist_item` + +Repraesentiert einen einzelnen Eintrag in einer Playlist. + +Felder: + +- `id` +- `playlist_id` +- `screen_id` +- `order_index` +- `media_asset_id` nullable +- `type` (`image`, `video`, `pdf`, `web`, `dir`) +- `src` +- `title` +- `duration_seconds` +- `load_timeout_seconds` +- `cache_policy` (`required`, `prefer_cache`, `no_cache`) +- `on_error` (`skip`, `retry`, `fallback`) +- `retry_count` +- `valid_from` nullable +- `valid_until` nullable +- `enabled` +- `created_at` +- `updated_at` + +Regeln: + +- `media_asset_id` wird bei verwalteten Medien gesetzt +- `src` enthaelt den effektiven Pfad oder die URL, damit der Player auch ohne Join-Struktur arbeiten kann +- `type=dir` erlaubt definierte Verzeichnisreferenzen innerhalb der Playlist + +### `playlist_item_dir_rule` + +Optionale Zusatzregel fuer `type=dir`. + +Felder: + +- `id` +- `playlist_item_id` +- `directory_path` +- `sort_mode` (`name_asc`, `name_desc`, `mtime_asc`, `mtime_desc`, `shuffle`) +- `per_item_duration_seconds` +- `recursive` +- `file_filter` nullable + +Zweck: + +- sauberere Modellierung fuer Verzeichnisbasierte Fallbacks oder Einschuebe + +### `screen_status` + +Repraesentiert den letzten bekannten Laufzeitstatus eines Players. + +Felder: + +- `screen_id` +- `online` +- `server_connected` +- `mqtt_connected` +- `last_heartbeat_at` +- `last_sync_at` +- `current_playlist_id` +- `current_playlist_item_id` nullable +- `current_item_type` nullable +- `current_item_label` nullable +- `current_item_started_at` nullable +- `current_item_duration_seconds` nullable +- `cache_state` (`ok`, `stale`, `missing`, `error`) +- `overlay_state` (`online`, `degraded`, `offline`) +- `error_code` nullable +- `error_message` nullable +- `player_version` +- `uptime_seconds` +- `free_disk_bytes` nullable +- `temperature_celsius` nullable +- `updated_at` + +Hinweise: + +- `screen_status` ist eine verdichtete Sicht fuer Dashboard und Ueberwachung +- historische Verlaeufe koennen spaeter separat gespeichert werden + +### `screen_snapshot` + +Repraesentiert eine Vorschauaufnahme des aktuellen Bildschirminhalts. + +Felder: + +- `id` +- `screen_id` +- `captured_at` +- `storage_path` +- `width` +- `height` +- `mime_type` +- `source` (`scheduled`, `item_change`, `manual`) + +Regeln: + +- in der UI wird typischerweise nur der letzte Snapshot angezeigt +- aeltere Snapshots koennen nach Zeit oder Anzahl aufgeraeumt werden + +### `device_command` + +Repraesentiert einen an einen Screen gesendeten Befehl. + +Felder: + +- `id` +- `screen_id` +- `command_type` (`reload`, `restart_player`, `reboot`, `display_on`, `display_off`, `refresh_snapshot`, `clear_cache`) +- `payload_json` +- `requested_by_user_id` +- `requested_at` +- `delivery_state` (`queued`, `sent`, `acknowledged`, `failed`, `expired`) +- `delivered_at` nullable +- `acknowledged_at` nullable +- `result_code` nullable +- `result_message` nullable + +Zweck: + +- nachvollziehbare Fernsteuerung mit Audit-Trail + +### `sync_state` + +Repraesentiert den Synchronisationsstand eines Screens. + +Felder: + +- `screen_id` +- `config_revision` +- `playlist_revision` +- `media_revision` +- `last_successful_sync_at` +- `last_failed_sync_at` nullable +- `last_error_message` nullable + +Zweck: + +- Erkennung, ob ein Player auf dem aktuellen Stand ist + +## Beziehungen + +- ein `tenant` hat viele `users` +- ein `tenant` hat viele `screens` +- ein `tenant` hat viele `media_assets` +- ein `screen` hat genau eine aktive `playlist` in v1 +- eine `playlist` hat viele `playlist_items` +- ein `screen` hat genau einen aktuellen `screen_status` +- ein `screen` hat viele `screen_snapshots` +- ein `screen` hat viele `device_commands` +- ein `screen` hat genau einen aktuellen `sync_state` + +## Zugriffsregeln + +### Tenant-User + +Ein `tenant_user` darf: + +- nur den eigenen `tenant` sehen +- nur den eigenen `screen` bzw. die dem Tenant zugeordneten Screens sehen +- nur eigene Medien hochladen und pflegen +- nur eigene Playlists bearbeiten +- nur die Vorschau des eigenen Screens sehen + +Ein `tenant_user` darf nicht: + +- andere Tenants sehen +- globale Steuerkommandos senden +- andere Screens administrieren + +### Admin + +Ein `admin` darf: + +- alle Tenants, Screens, Medien und Playlists sehen +- globale Steuerbefehle senden +- Vorschau und Status aller Screens sehen +- neue Screens und Benutzer anlegen + +## Revisions- und Caching-Modell + +Damit Offline-Betrieb einfach bleibt, arbeitet der Player nicht gegen beliebige Einzelobjekte, sondern gegen Revisionen. + +Sinnvoll sind mindestens: + +- `playlist_revision` +- `media_revision` +- `config_revision` + +Der Player kann damit erkennen: + +- ob neue Konfiguration vorliegt +- ob Medien nachgeladen werden muessen +- ob die lokale Kopie noch gueltig ist + +## Beispiel fuer aktive Playlist-Auswertung + +Ein `playlist_item` ist aktiv, wenn: + +- `enabled = true` +- `valid_from` leer oder in der Vergangenheit liegt +- `valid_until` leer oder in der Zukunft liegt + +Wenn kein aktives Item existiert: + +- greift die globale Fallback-Regel des Screens oder der Playlist + +## Beispiel fuer Admin-Steuerung + +Wenn ein Admin `reload` ausloest: + +1. Ein `device_command` wird gespeichert +2. der Befehl wird per MQTT an den Screen signalisiert +3. der Player fuehrt den Reload aus +4. der Player sendet ein ACK +5. `device_command.delivery_state` wird aktualisiert + +## Offene spaetere Erweiterungen + +- mehrere Screens pro Tenant in der Firmenoberflaeche +- Vorlagen und globale Playlists +- Historie der Statusdaten als Zeitreihe +- Geplante Kampagnen mit Prioritaeten +- serverseitige Konvertierung oder Transkodierung von Medien diff --git a/PLAN.md b/PLAN.md new file mode 100644 index 0000000..cb5abbd --- /dev/null +++ b/PLAN.md @@ -0,0 +1,416 @@ +# Info-Board Neu - Architekturplan + +## Zielbild + +Das neue System ersetzt die bestehende `pictur`-/LXDE-basierte Loesung durch eine schlanke, zentral verwaltete Signage-Plattform. + +Ziele: + +- moeglichst wenig Ballast auf den Player-Clients +- Anzeige von Bildern, Videos, PDFs und Webseiten +- flexible Playlist pro Monitor +- Fallback auf sequentielle Anzeige aller Medien aus einem konfigurierten Verzeichnis +- mandantenfaehige Pflegeoberflaeche pro Monitor/Firma +- Admin-Oberflaeche fuer Gesamtsteuerung aller Monitore +- autonomer Offline-Betrieb bei Serverausfall +- einfache Verteilung per Ansible +- moeglichst geringe Abhaengigkeit vom Unterbau innerhalb Debian-/Raspberry-Pi-OS-Welt + +## Basisentscheidungen + +### Betriebssystem + +- Player-Basis: aktuelles Raspberry Pi OS Lite auf Debian-13-Basis +- Server-Basis: Debian-artiges System, bevorzugt containerisiert +- keine volle Desktop-Umgebung auf den Playern + +### Display-Stack + +- fuer Version 1 X11 statt Wayland +- Grund: konservativer, besser vorhersehbar fuer Chromium-Kiosk, Screenshot-Erzeugung, Watchdogs und Raspberry-spezifische Display-Steuerung +- kein LXDE, kein LightDM als Komfortschicht, nur ein minimaler X11-Kiosk-Stack + +### Container-Strategie + +- Server: Docker Compose ist sinnvoll und empfohlen +- Player: kein Voll-Docker-Ansatz fuer den Grafikpfad in Version 1 +- Player besser als native Dienste mit systemd + Chromium-Kiosk + +## Gesamtarchitektur + +Das System besteht aus zwei Hauptteilen: + +1. zentraler Server fuer Verwaltung, API, Medien, Rechte und Steuerung +2. schlanke Player-Clients auf den Monitoren + +### Server-Komponenten + +- Reverse Proxy +- Backend API +- Admin-Oberflaeche +- mandantenbezogene Management-Oberflaeche +- PostgreSQL +- MQTT-Broker +- Dateispeicher fuer Uploads, Vorschaubilder und Screenshots + +### Player-Komponenten + +- `player-agent` als nativer systemd-Dienst +- `player-ui` als lokale Web-Anwendung +- Chromium im Kiosk-Modus, der nur die lokale `player-ui` rendert +- lokaler Cache fuer Playlist, Medien und Status + +## Rollenmodell + +### Monitor-/Firmen-Oberflaeche + +Jede Firma sieht nur den ihr zugeordneten Monitor bzw. Kanal. + +Funktionen: + +- Medien hochladen +- Medien loeschen und ordnen +- Playlist pflegen +- Anzeigedauer je Eintrag festlegen +- `valid_from` und `valid_until` setzen +- lokale Vorschau des eigenen Monitors sehen + +### Admin-Oberflaeche + +Die Administration der Schule sieht und steuert die gesamte Anlage. + +Funktionen: + +- Inhalte aller Monitore verwalten +- Monitore global uebersichtlich sehen +- Vorschau/Screenshots aller Monitore sehen +- Reload des Players ausloesen +- Seite neu laden +- Dienste neu starten +- Monitore rebooten +- Monitore an- und ausschalten + +## Datenmodell + +### Zentrale Entitaeten + +- `tenant` +- `screen` +- `user` +- `media_asset` +- `playlist` +- `playlist_item` +- `screen_status` +- `screen_snapshot` +- `device_command` +- `sync_state` + +### Wichtige Felder + +#### `screen` + +- `id` +- `name` +- `tenant_id` +- `location` +- `rotation` +- `resolution` +- `enabled` + +#### `media_asset` + +- `id` +- `tenant_id` +- `type` (`image`, `video`, `pdf`, `web`) +- `source_kind` (`upload`, `remote_url`) +- `storage_path` +- `original_url` +- `checksum` +- `mime_type` +- `created_at` + +#### `playlist_item` + +- `id` +- `playlist_id` +- `screen_id` +- `order_index` +- `type` +- `src` +- `duration` +- `valid_from` +- `valid_until` +- `load_timeout` +- `cache_policy` +- `on_error` +- `enabled` + +#### `screen_status` + +- `screen_id` +- `online` +- `last_heartbeat` +- `current_item` +- `current_type` +- `current_since` +- `last_sync_at` +- `server_connected` +- `cache_state` +- `error_state` + +#### `screen_snapshot` + +- `screen_id` +- `captured_at` +- `image_path` + +## Playlist-Konzept + +### Unterstuetzte Typen + +- Bild +- Video +- PDF +- Webseite +- Verzeichnisreferenz als Fallback-/Sammelquelle + +### Pro Eintrag steuerbar + +- Dauer +- Gueltigkeit ueber `valid_from` und `valid_until` +- Reihenfolge +- Timeout fuer das Laden +- Cache-Strategie +- Fehlerverhalten (`skip`, `retry`, `fallback`) + +### Fallback-Regel + +Wenn keine aktive Playlist vorhanden ist oder kein gueltiges Item aktiv ist, zeigt der Player alle Medien aus einem konfigurierten Fallback-Verzeichnis nacheinander an. + +Eigenschaften: + +- deterministische Reihenfolge, standardmaessig alphabetisch +- medientypgerechte Anzeigezeiten +- funktioniert komplett lokal aus dem Cache oder Dateisystem + +## Offline- und Cache-Strategie + +Offline-Betrieb ist Pflicht. + +Der Player speichert lokal: + +- die letzte gueltige Playlist +- alle noetigen Medien +- letzten bekannten Status +- letzte Server-/Sync-Metadaten + +Bei Serverausfall: + +- Wiedergabe laeuft lokal weiter +- ein kleines Overlay-Symbol zeigt den Offline-Zustand an +- Heartbeat wird lokal gepuffert oder beim Wiederverbinden aktualisiert + +### Overlay-Zustaende + +- gruen: online +- gelb: Verbindung instabil oder Daten veraltet +- rot: keine Verbindung zum Server + +## Vermeidung von Chromium-Haengern + +Chromium rendert niemals direkt externe Medien- oder Playlist-URLs. + +Stattdessen: + +- Chromium zeigt nur `http://127.0.0.1:/player` +- die lokale `player-ui` kapselt alle Medien und Fehlerzustande +- externe Medien werden vorher geladen oder lokal gespiegelt, wo immer moeglich + +Konsequenzen: + +- keine haengenden 404-Fehlerseiten als Hauptansicht +- Timeouts pro Inhalt moeglich +- bei Fehlern: Skip oder Fallback statt Stehenbleiben +- lokaler Watchdog kann Anzeige und Browser neu initialisieren + +## Vorschaukonzept + +Eine Vorschau ist sinnvoll, aber nicht als permanenter Livestream. + +Empfohlene Umsetzung: + +- Screenshot bei Item-Wechsel +- zusaetzlicher Screenshot in Intervallen, z. B. alle 30 bis 60 Sekunden +- Anzeige des letzten Screenshots in Admin- und Firmen-Oberflaeche +- ergaenzt durch strukturierte Statusdaten + +Statusdaten: + +- aktuelles Item +- Typ +- Startzeit +- Restlaufzeit +- letzter Heartbeat +- letzter Sync +- Fehlerstatus + +## Kommunikation + +### HTTPS + +Fuer: + +- Login +- Uploads +- API-Aufrufe +- Playlist-Bearbeitung +- Download von Medien und Konfiguration +- Abruf von Screenshots und Status + +### MQTT + +Fuer: + +- Heartbeat +- Statusmeldungen +- Kommandos +- Acknowledgements +- Hinweis auf Playlist-Aenderungen + +### MQTT-Topics + +- `signage/screen//heartbeat` +- `signage/screen//status` +- `signage/screen//event` +- `signage/screen//command` +- `signage/screen//ack` + +### Befehle + +- `reload` +- `restart_player` +- `reboot` +- `display_on` +- `display_off` +- `refresh_snapshot` +- `clear_cache` + +## Player-Aufbau + +### `player-agent` + +Aufgaben: + +- Registrierung des Geraets +- HTTPS-Sync +- MQTT-Kommunikation +- Cache-Verwaltung +- Befehlsausfuehrung +- Screenshot-Erzeugung +- Ueberwachung lokaler Dienste + +### `player-ui` + +Aufgaben: + +- lokale Wiedergabeoberflaeche +- Darstellung von Bild, Video, PDF und Web +- Auswertung von Zeitfenstern +- Umschalten zwischen Playlist und Fallback +- Offline-/Fehler-Overlay + +## Server-Aufbau + +### Kernkomponenten + +- API-Backend +- Admin-Frontend +- Tenant-Frontend +- MQTT-Broker +- Datenbank +- Media-Storage + +### Betriebsform + +- bevorzugt per Docker Compose +- klare Trennung von persistenten Daten, Konfiguration und Deploy-Code + +## Minimale Abhaengigkeiten auf dem Player + +Das Ziel ist ein moeglichst kleiner Satz an Laufzeitabhaengigkeiten. + +Bevorzugt: + +- Raspberry Pi OS Lite +- Xorg-Minimalstack +- Chromium +- ein eigenes Player-Binary oder eine sehr schlanke Laufzeit +- systemd + +Vermeiden: + +- voller Desktop +- mehrere externe Viewer wie `feh`, `vlc`, `wmctrl`, `xdg-open` +- Python-/Node-Laufzeiten mit grossem Paketbaum direkt auf dem Client, wenn vermeidbar + +## Ansible-Zielstruktur + +### Rollen + +- `signage_base` +- `signage_player` +- `signage_display` +- `signage_server` + +### Konfigurationspfade auf dem Client + +- `/etc/signage/config.yml` +- `/var/lib/signage/cache/` +- `/var/lib/signage/media/` +- `/var/lib/signage/state/` +- `/var/log/signage/` + +## Migrationsstrategie + +1. Architektur finalisieren +2. Datenmodell finalisieren +3. Minimalen Player-Prototyp bauen +4. Server-Minimalversion bauen +5. Referenz-Raspi aufsetzen +6. Offline-/Netzfehler-/Timeout-Szenarien testen +7. Management-Oberflaechen ausbauen +8. Ansible-Rollen erstellen +9. Einen Pilotmonitor migrieren +10. Restliche Monitore sukzessive migrieren + +## Teststrategie + +Zu testen sind mindestens: + +- kurzer Netzverlust +- kompletter Serverausfall +- MQTT-Ausfall bei funktionierendem HTTPS +- HTTPS-Ausfall bei funktionierendem MQTT +- kaputte URL in Playlist +- defekte PDF-Datei +- grosses Video +- Wechsel von `valid_from`/`valid_until` waehrend des laufenden Betriebs +- Reload aus der Admin-Oberflaeche +- Dienstneustart aus der Admin-Oberflaeche +- Reboot aus der Admin-Oberflaeche +- Display an/aus +- Screenshot-Erzeugung unter Last + +## Projektstruktur-Empfehlung + +Ein separates Projektverzeichnis ist sinnvoll, um die neue Architektur sauber von der bestehenden Netboot-Struktur zu trennen. + +Empfohlener Pfad: + +- `/srv/docker/info-board-neu` + +Dieses Verzeichnis sollte mindestens enthalten: + +- `PLAN.md` +- `TODO.md` +- spaeter `compose/`, `ansible/`, `server/`, `player/`, `docs/` diff --git a/README.md b/README.md new file mode 100644 index 0000000..ef6a042 --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +# Info-Board Neu + +Dieses Verzeichnis enthaelt die Planung und spaetere Umsetzung der neuen Info-Board-Plattform. + +Die Trennung von `/srv/docker/infoboard-netboot` ist sinnvoll, damit: + +- die bestehende produktive Netboot-Struktur unangetastet bleibt +- Planung, Prototypen und neue Deployments sauber getrennt sind +- Server-, Player- und Ansible-Artefakte nicht mit Altbestand vermischt werden + +## Aktueller Stand + +- Architekturplan: `PLAN.md` +- Umsetzungs-Todo: `TODO.md` +- Datenmodell: `DATENMODELL.md` +- API- und MQTT-Vertrag: `API-MQTT-VERTRAG.md` + +## Empfohlene spaetere Struktur + +- `docs/` fuer weitere Architektur- und Betriebsdokumente +- `server/` fuer Backend, Frontends und Compose-Dateien +- `player/` fuer Agent, UI und lokale Startlogik +- `ansible/` fuer Rollen, Inventories und Deployments +- `scripts/` fuer Hilfsskripte diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..a6b4ca0 --- /dev/null +++ b/TODO.md @@ -0,0 +1,132 @@ +# Info-Board Neu - Umsetzungs-Todo + +## Phase 0 - Projektbasis + +- [ ] Projektverzeichnisstruktur unter `/srv/docker/info-board-neu` festlegen +- [ ] Namenskonventionen fuer Server, Player, Rollen und Pakete definieren +- [ ] Dokumentationsstruktur fuer Architektur, Betrieb und Deployment anlegen +- [ ] Entscheidung fuer Server-Tech-Stack dokumentieren +- [ ] Entscheidung fuer Player-Implementierung dokumentieren + +## Phase 1 - Fachliches Fundament + +- [ ] Rollenmodell fuer `admin` und monitorgebundene Nutzer final festschreiben +- [ ] Datenmodell fuer `tenant`, `screen`, `user`, `media_asset`, `playlist`, `playlist_item`, `screen_status`, `screen_snapshot` definieren +- [ ] Playlist-Semantik mit `duration`, `valid_from`, `valid_until`, `load_timeout`, `cache_policy`, `on_error` spezifizieren +- [ ] Fallback-Regel fuer ungeplante oder leere Inhalte verbindlich definieren +- [ ] Statusmodell fuer Online/Offline/Degraded/Error definieren +- [ ] Kommandokatalog fuer Admin-Aktionen finalisieren + +## Phase 2 - Technische Zielarchitektur + +- [ ] Server-Komponentenliste finalisieren +- [ ] API-Schnittstellen grob definieren +- [ ] MQTT-Topic-Struktur finalisieren +- [ ] HTTPS- und MQTT-Aufgabentrennung dokumentieren +- [ ] Screenshot-/Vorschaustrategie spezifizieren +- [ ] Offline- und Cache-Strategie bis auf Dateiebene festlegen +- [ ] Sicherheitsmodell fuer Uploads, Login und Rechte pruefen + +## Phase 3 - Player-Design + +- [ ] Minimalen Paketbedarf fuer den Player auf Raspberry Pi OS Debian 13 ermitteln +- [ ] X11-Minimalkonzept fuer Chromium-Kiosk dokumentieren +- [ ] Startmechanismus fuer Chromium ohne Desktop-Umgebung definieren +- [ ] Verzeichnislayout auf dem Player festlegen +- [ ] `player-agent` fachlich zuschneiden +- [ ] `player-ui` fachlich zuschneiden +- [ ] Watchdog-Konzept fuer Browser und Agent definieren +- [ ] Offline-Overlay-Verhalten spezifizieren +- [ ] Fehlerbehandlung fuer Web-Inhalte und Timeouts ausarbeiten +- [ ] Display-Steuerung fuer An/Aus, Rotation und Neustart planen + +## Phase 4 - Server-Design + +- [ ] API-Backend fachlich schneiden +- [ ] Admin-Oberflaeche in Hauptbereiche aufteilen +- [ ] Firmen-/Monitor-Oberflaeche in Hauptbereiche aufteilen +- [ ] Storage-Konzept fuer Uploads, Cache-Dateien und Screenshots festlegen +- [ ] Authentifizierungskonzept festlegen +- [ ] Mandantentrennung im Datenmodell und in den APIs absichern +- [ ] Logging- und Monitoring-Konzept definieren + +## Phase 5 - Prototyping + +- [ ] Minimalen Server-Prototyp bauen +- [ ] Minimalen Player-Agent-Prototyp bauen +- [ ] Minimale Player-UI bauen +- [ ] Lokale Test-Playlist mit Bild, Video, PDF und Webseite anlegen +- [ ] Fallback-Verzeichnisbetrieb demonstrieren +- [ ] `valid_from`/`valid_until` im Prototyp pruefen +- [ ] Offline-Sync mit lokalem Cache pruefen +- [ ] MQTT-Kommandos `reload`, `restart_player`, `reboot`, `display_on`, `display_off` testweise durchspielen + +## Phase 6 - Betriebsfaehigkeit + +- [ ] Docker-Compose-Setup fuer den Server anlegen +- [ ] systemd-Units fuer den Player erstellen +- [ ] Chromium-Kiosk-Startskript erstellen +- [ ] Screenshot-Erzeugung auf dem Player integrieren +- [ ] Heartbeat- und Statusmeldungen integrieren +- [ ] Fehler- und Wiederanlaufverhalten verifizieren + +## Phase 7 - Ansible-Automatisierung + +- [ ] Rolle `signage_base` erstellen +- [ ] Rolle `signage_player` erstellen +- [ ] Rolle `signage_display` erstellen +- [ ] Rolle `signage_server` erstellen +- [ ] Inventar-/Variablenmodell fuer mehrere Monitore entwerfen +- [ ] Screen-spezifische Variablen wie `screen_id`, Rotation und Aufloesung abbilden +- [ ] Erstinstallation eines neuen Players automatisieren +- [ ] Update-Rollout eines bestehenden Players automatisieren + +## Phase 8 - Pilotbetrieb + +- [ ] Einen Referenzmonitor fuer den Pilotbetrieb auswaehlen +- [ ] Pilotmonitor neu aufsetzen +- [ ] Verbindung zum Zentralserver herstellen +- [ ] Upload- und Playlist-Pflege mit einem Testmandanten pruefen +- [ ] Admin-Funktionen am Pilotmonitor pruefen +- [ ] Offline-Betrieb real testen +- [ ] Browser-/Renderer-Stabilitaet ueber laengeren Zeitraum beobachten + +## Phase 9 - Migration der Bestandsmonitore + +- [ ] Migrationsreihenfolge fuer `info01` bis `info09` festlegen +- [ ] Rueckfallstrategie pro Monitor definieren +- [ ] Altinhalte in das neue Medienmodell ueberfuehren +- [ ] Playlists uebernehmen oder neu modellieren +- [ ] Monitore nacheinander umstellen +- [ ] Nach jeder Umstellung Stabilitaet und Fernsteuerung pruefen + +## Phase 10 - Nacharbeiten + +- [ ] Betriebsdokumentation schreiben +- [ ] Admin-Handbuch schreiben +- [ ] Kurzhandbuch fuer Firmen-Nutzer schreiben +- [ ] Backup- und Restore-Konzept dokumentieren +- [ ] Update- und Release-Prozess festlegen +- [ ] Langfristige Wayland-Neubewertung fuer spaetere Version vormerken + +## Querschnittsthemen + +- [ ] Datensicherung fuer Datenbank und Medien einplanen +- [ ] TLS-/Reverse-Proxy-Konzept festlegen +- [ ] Ressourcenverbrauch auf Raspberry Pi beobachten +- [ ] Verhalten bei grossen Videos optimieren +- [ ] Verhalten bei defekten externen Webseiten absichern +- [ ] Datenschutz- und Rechtefragen fuer Screenshots/Vorschauen pruefen + +## Erste konkrete Abarbeitungsreihenfolge + +- [ ] 1. Projektstruktur im neuen Verzeichnis vervollstaendigen +- [ ] 2. Datenmodell in eigener Datei ausformulieren +- [ ] 3. API- und MQTT-Vertrag definieren +- [ ] 4. Player-Minimalkonzept fuer Raspberry Pi OS Debian 13 festzurren +- [ ] 5. Server-Compose-Grundgeruest erstellen +- [ ] 6. Player-Prototyp mit lokalem Browser-Renderer bauen +- [ ] 7. Offline-Cache und Fallback robust machen +- [ ] 8. UIs fuer Admin und Firmen schrittweise aufbauen +- [ ] 9. Ansible-Rollen erstellen +- [ ] 10. Pilotmonitor migrieren