# Info-Board Neu - MQTT-Vertrag

Vertrag zwischen Backend und Agent für die Echtzeit-Synchronisation von Playlist-Änderungen und Gerätebefehlen.

---

## Überblick

Das Messaging-System nutzt MQTT für:

- **Playlist-Mutations-Benachrichtigungen**: Backend → Agent
- **Device-Commands**: Backend → Agent (zukünftig: reload, restart_player, reboot, display_on/off)
- **Heartbeat & Status**: Agent → Backend (siehe `PLAYER-STATUS-HTTP.md`)

Alle Topics folgen dem Naming-Pattern: `signage/{component}/{screenSlug}/{event}`

---

## Topics

### Backend publishes

#### `signage/screen/{screenSlug}/playlist-changed`

**Publisher:** Backend
**Subscriber:** Agent

Wird nach jeder Mutation der Playlist gepublished (Add, Remove, Reorder, Enable/Disable Item).

**Payload:**
```json
{
  "ts": 1711268440000
}
```

- `ts`: Unix-Zeitstempel in Millisekunden des Änderungsereignisses auf dem Backend

**Verhalten:**
- Backend debounced Änderungen über **2 Sekunden**
- Mehrere schnelle Mutationen werden zu einem Event zusammengefasst
- Garantiert mindestens ein Event pro logischer Änderung

**Agent-Reaktion:**
- Agent empfängt das Event
- Agent debounced die Verarbeitung über **3 Sekunden**
- Agent startet sofortiges Playlist-Fetch via HTTP `GET /api/v1/screens/{screenSlug}/playlist`
- Agent speichert die Playlist lokal und signalisiert dem Browser einen Reload

**Implementierung (Agent):**
```go
// Pseudocode
func OnPlaylistChanged(msg PlaylistChangedMessage) {
    if debounceTimer.running {
        debounceTimer.reset()
    } else {
        debounceTimer.start(3 * time.Second)
    }
}

func onDebounceExpire() {
    playlist := fetchPlaylistViaHTTP()
    saveToLocalCache(playlist)
    signalBrowserReload()
}
```

---

## Zukünftige Topics

Die folgenden Topics sind **geplant** für Phase 5 (Prototyping) und später:

### `signage/screen/{screenSlug}/device-command`

**Publisher:** Backend
**Subscriber:** Agent

Befehl-Queue für Device-Steuerung.

**Payload:**
```json
{
  "cmd_id": "uuid",
  "command": "reload|restart_player|reboot|display_on|display_off",
  "ts": 1711268440000
}
```

**Agent-Reaktion:**
- Befehl ausführen
- ACK via HTTP POST zu `PUT /api/v1/screens/{screenSlug}/command-ack`

---

## Beispiel-Flow: Playlist-Update

```
Admin: Click "Speichern" in Playlist-UI
  ↓
Backend: Playlist-Mutation in DB schreiben
  ↓
Backend: `playlist-changed` mit ts=now nach 2s Debounce publifyen
  ↓
Agent: Event empfangen, 3s Debounce starten
  ↓
Agent: Nach 3s → HTTP GET /api/v1/screens/{slug}/playlist
  ↓
Backend: Aktuelle Playlist zurückgeben
  ↓
Agent: Lokal speichern, Browser signalisieren "reload"
  ↓
Browser: Neuer Content geladen und abgespielt
```

---

## MQTT-Verbindungspezifikation

(Siehe `PLAYER-KONZEPT.md` und Provisioning-Variablen für Broker-URL, Authentifizierung und Retry-Logik)

- **Broker-Adresse:** Über Provisioning konfigurierbar (Standard: `tcp://backend:1883`)
- **Client-ID:** `{tenantSlug}/{screenSlug}` (eindeutig pro Screen)
- **Username/Password:** Device-spezifische Credentials (OAuth-ähnlich)
- **QoS-Level:** 1 (At-Least-Once für Critical-Events)
- **Retain:** nein (Event-Natur, nicht State)
- **Heartbeat:** Separat via HTTP (siehe `PLAYER-STATUS-HTTP.md`)

---

## Notizen für Implementierer

1. **Replay bei Reconnect:** Topics haben `retain: false`, daher entfallen keine Events bei Trennung. Der Agent synchronisiert sich nach Reconnect via regulärem Status-Endpoint.
2. **Ordering:** Mehrere Events zu einem Screen sind ordered; Ordering über Screen-Grenzen hinweg ist nicht garantiert.
3. **Fehlerbehandlung:** Fehlgeschlagene Playlisten-Fetches werden vom Agent nach Standard-Retry-Logik wiederholt.
4. **Version der Spec:** v1.0 (März 2026)
