339 lines
9.4 KiB
Markdown
339 lines
9.4 KiB
Markdown
# MQTT Event Payload Guide
|
|
|
|
## Overview
|
|
|
|
This document describes the MQTT message structure used by the Infoscreen system to deliver event information from the scheduler to display clients. It covers best practices, payload formats, and versioning strategies.
|
|
|
|
## MQTT Topics
|
|
|
|
### Event Distribution
|
|
- **Topic**: `infoscreen/events/{group_id}`
|
|
- **Retained**: Yes
|
|
- **Format**: JSON array of event objects
|
|
- **Purpose**: Delivers active events to client groups
|
|
|
|
### Per-Client Configuration
|
|
- **Topic**: `infoscreen/{uuid}/group_id`
|
|
- **Retained**: Yes
|
|
- **Format**: Integer (group ID)
|
|
- **Purpose**: Assigns clients to groups
|
|
|
|
### TV Power Intent (Phase 1)
|
|
- **Topic**: `infoscreen/groups/{group_id}/power/intent`
|
|
- **QoS**: 1
|
|
- **Retained**: Yes
|
|
- **Format**: JSON object
|
|
- **Purpose**: Group-level desired power state for clients assigned to that group
|
|
|
|
Phase 1 is group-only. Per-client power intent topics and client state/ack topics are deferred to Phase 2.
|
|
|
|
Example payload:
|
|
|
|
```json
|
|
{
|
|
"schema_version": "tv-power-intent.v1",
|
|
"intent_id": "9cf26d9b-87a3-42f1-8446-e90bb6f6ce63",
|
|
"group_id": 12,
|
|
"desired_state": "on",
|
|
"reason": "active_event",
|
|
"issued_at": "2026-03-31T10:15:30Z",
|
|
"expires_at": "2026-03-31T10:17:00Z",
|
|
"poll_interval_sec": 30,
|
|
"source": "scheduler"
|
|
}
|
|
```
|
|
|
|
Contract notes:
|
|
- `intent_id` changes only on semantic transition (`desired_state`/`reason` changes).
|
|
- Heartbeat republishes keep `intent_id` stable while refreshing `issued_at` and `expires_at`.
|
|
- Expiry is poll-based: `max(3 x poll_interval_sec, 90)`.
|
|
|
|
## Message Structure
|
|
|
|
### General Principles
|
|
|
|
1. **Type Safety**: Always include `event_type` to allow clients to parse appropriately
|
|
2. **Backward Compatibility**: Add new fields without removing old ones
|
|
3. **Extensibility**: Use nested objects for event-type-specific data
|
|
4. **UTC Timestamps**: All times in ISO 8601 format with timezone info
|
|
|
|
### Base Event Structure
|
|
|
|
Every event includes these common fields:
|
|
|
|
```json
|
|
{
|
|
"id": 123,
|
|
"title": "Event Title",
|
|
"start": "2025-10-19T09:00:00+00:00",
|
|
"end": "2025-10-19T09:30:00+00:00",
|
|
"group_id": 1,
|
|
"event_type": "presentation|website|webuntis|video|message|other",
|
|
"recurrence_rule": "FREQ=WEEKLY;BYDAY=MO,WE,FR" or null,
|
|
"recurrence_end": "2025-12-31T23:59:59+00:00" or null
|
|
}
|
|
```
|
|
|
|
### Event Type-Specific Payloads
|
|
|
|
#### Presentation Events
|
|
|
|
```json
|
|
{
|
|
"id": 123,
|
|
"event_type": "presentation",
|
|
"title": "Morning Announcements",
|
|
"start": "2025-10-19T09:00:00+00:00",
|
|
"end": "2025-10-19T09:30:00+00:00",
|
|
"group_id": 1,
|
|
"presentation": {
|
|
"type": "slideshow",
|
|
"files": [
|
|
{
|
|
"name": "slides.pdf",
|
|
"url": "http://server:8000/api/files/converted/abc123.pdf",
|
|
"checksum": null,
|
|
"size": null
|
|
}
|
|
],
|
|
"slide_interval": 10000,
|
|
"auto_advance": true,
|
|
"page_progress": true,
|
|
"auto_progress": true
|
|
}
|
|
}
|
|
```
|
|
|
|
**Fields**:
|
|
- `type`: Always "slideshow" for presentations
|
|
- `files`: Array of file objects with download URLs
|
|
- `slide_interval`: Milliseconds between slides (default: 5000)
|
|
- `auto_advance`: Whether to automatically advance slides
|
|
- `page_progress`: Show page number indicator
|
|
- `auto_progress`: Enable automatic progression
|
|
|
|
#### Website Events
|
|
|
|
```json
|
|
{
|
|
"id": 124,
|
|
"event_type": "website",
|
|
"title": "School Website",
|
|
"start": "2025-10-19T09:00:00+00:00",
|
|
"end": "2025-10-19T09:30:00+00:00",
|
|
"group_id": 1,
|
|
"website": {
|
|
"type": "browser",
|
|
"url": "https://example.com/page"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Fields**:
|
|
- `type`: Always "browser" for website display
|
|
- `url`: Full URL to display in embedded browser
|
|
|
|
#### WebUntis Events
|
|
|
|
```json
|
|
{
|
|
"id": 125,
|
|
"event_type": "webuntis",
|
|
"title": "Schedule Display",
|
|
"start": "2025-10-19T09:00:00+00:00",
|
|
"end": "2025-10-19T09:30:00+00:00",
|
|
"group_id": 1,
|
|
"website": {
|
|
"type": "browser",
|
|
"url": "https://webuntis.example.com/schedule"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Note**: WebUntis events use the same payload structure as website events. The URL is fetched from system settings (`webuntis_url`) rather than being specified per-event. Clients treat `webuntis` and `website` event types identically—both display a website.
|
|
|
|
#### Video Events
|
|
|
|
```json
|
|
{
|
|
"id": 126,
|
|
"event_type": "video",
|
|
"title": "Video Playback",
|
|
"start": "2025-10-19T09:00:00+00:00",
|
|
"end": "2025-10-19T09:30:00+00:00",
|
|
"group_id": 1,
|
|
"video": {
|
|
"type": "media",
|
|
"url": "http://server:8000/api/eventmedia/stream/123/video.mp4",
|
|
"autoplay": true,
|
|
"loop": false,
|
|
"volume": 0.8
|
|
}
|
|
}
|
|
```
|
|
|
|
**Fields**:
|
|
- `type`: Always "media" for video playback
|
|
- `url`: Video streaming URL with range request support
|
|
- `autoplay`: Whether to start playing automatically (default: true)
|
|
- `loop`: Whether to loop the video (default: false)
|
|
- `volume`: Playback volume from 0.0 to 1.0 (default: 0.8)
|
|
|
|
#### Message Events (Future)
|
|
|
|
```json
|
|
{
|
|
"id": 127,
|
|
"event_type": "message",
|
|
"title": "Important Announcement",
|
|
"start": "2025-10-19T09:00:00+00:00",
|
|
"end": "2025-10-19T09:30:00+00:00",
|
|
"group_id": 1,
|
|
"message": {
|
|
"type": "html",
|
|
"content": "<h1>Important</h1><p>Message content</p>",
|
|
"style": "default"
|
|
}
|
|
}
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
### 1. Type-Based Parsing
|
|
|
|
Clients should:
|
|
1. Read the `event_type` field first
|
|
2. Switch/dispatch based on type
|
|
3. Parse type-specific nested objects (`presentation`, `website`, etc.)
|
|
|
|
```javascript
|
|
// Example client parsing
|
|
function parseEvent(event) {
|
|
switch (event.event_type) {
|
|
case 'presentation':
|
|
return handlePresentation(event.presentation);
|
|
case 'website':
|
|
case 'webuntis':
|
|
return handleWebsite(event.website);
|
|
case 'video':
|
|
return handleVideo(event.video);
|
|
// ...
|
|
}
|
|
}
|
|
```
|
|
|
|
### 2. Graceful Degradation
|
|
|
|
- Always provide fallback values for optional fields
|
|
- Validate URLs before attempting to load
|
|
- Handle missing or malformed data gracefully
|
|
|
|
### 3. Performance Optimization
|
|
|
|
- Cache downloaded presentation files
|
|
- Use checksums to avoid re-downloading unchanged content
|
|
- Preload resources before event start time
|
|
|
|
### 4. Time Handling
|
|
|
|
- Always parse ISO 8601 timestamps with timezone awareness
|
|
- Compare event start/end times in UTC
|
|
- Account for clock drift on embedded devices
|
|
|
|
### 5. Error Recovery
|
|
|
|
- Retry failed downloads with exponential backoff
|
|
- Log errors but continue operation
|
|
- Display fallback content if event data is invalid
|
|
|
|
## Message Flow
|
|
|
|
1. **Scheduler** queries active events from database
|
|
2. **Scheduler** formats events with type-specific payloads
|
|
3. **Scheduler** publishes JSON array to `infoscreen/events/{group_id}` (retained)
|
|
4. **Client** receives retained message on connect
|
|
5. **Client** parses events and schedules display
|
|
6. **Client** downloads resources (presentations, etc.)
|
|
7. **Client** displays events at scheduled times
|
|
|
|
## Versioning Strategy
|
|
|
|
### Adding New Event Types
|
|
|
|
1. Add enum value to `EventType` in `models/models.py`
|
|
2. Update scheduler's `format_event_with_media()` in `scheduler/db_utils.py`
|
|
3. Update events API in `server/routes/events.py`
|
|
4. Add icon mapping in `get_icon_for_type()`
|
|
5. Document payload structure in this guide
|
|
|
|
### Adding Fields to Existing Types
|
|
|
|
- **Safe**: Add new optional fields to nested objects
|
|
- **Unsafe**: Remove or rename existing fields
|
|
- **Migration**: Provide both old and new field names during transition
|
|
|
|
### Example: Adding a New Field
|
|
|
|
```json
|
|
{
|
|
"event_type": "presentation",
|
|
"presentation": {
|
|
"type": "slideshow",
|
|
"files": [...],
|
|
"slide_interval": 10000,
|
|
"transition_effect": "fade" // NEW FIELD (optional)
|
|
}
|
|
}
|
|
```
|
|
|
|
Old clients ignore unknown fields; new clients use enhanced features.
|
|
|
|
## Common Pitfalls
|
|
|
|
1. **Hardcoding Event Types**: Use `event_type` field, not assumptions
|
|
2. **Timezone Confusion**: Always use UTC internally
|
|
3. **Missing Error Handling**: Network failures, malformed URLs, etc.
|
|
4. **Resource Leaks**: Clean up downloaded files periodically
|
|
5. **Not Handling Recurrence**: Events may repeat; check `recurrence_rule`
|
|
|
|
## System Settings Integration
|
|
|
|
Some event types rely on system-wide settings rather than per-event configuration:
|
|
|
|
### WebUntis / Supplement Table URL
|
|
- **Setting Key**: `supplement_table_url`
|
|
- **API Endpoint**: `GET/POST /api/system-settings/supplement-table`
|
|
- **Usage**: Automatically applied when creating `webuntis` events
|
|
- **Default**: Empty string (must be configured by admin)
|
|
- **Description**: This URL is shared for both Vertretungsplan (supplement table) and WebUntis displays
|
|
|
|
### Presentation Defaults
|
|
- `presentation_interval`: Default slide interval (seconds)
|
|
- `presentation_page_progress`: Show page indicators by default
|
|
- `presentation_auto_progress`: Auto-advance by default
|
|
|
|
These are applied when creating new events but can be overridden per-event.
|
|
|
|
## Testing Recommendations
|
|
|
|
1. **Unit Tests**: Validate payload serialization/deserialization
|
|
2. **Integration Tests**: Full scheduler → MQTT → client flow
|
|
3. **Edge Cases**: Empty event lists, missing URLs, malformed data
|
|
4. **Performance Tests**: Large file downloads, many events
|
|
5. **Time Tests**: Events across midnight, timezone boundaries, DST
|
|
|
|
## Related Documentation
|
|
|
|
- `AUTH_SYSTEM.md` - Authentication and authorization
|
|
- `DATABASE_GUIDE.md` - Database schema and models
|
|
- `.github/copilot-instructions.md` - System architecture overview
|
|
- `scheduler/scheduler.py` - Event publishing implementation
|
|
- `scheduler/db_utils.py` - Event formatting logic
|
|
|
|
## Changelog
|
|
|
|
- **2025-10-19**: Initial documentation
|
|
- Documented base event structure
|
|
- Added presentation and website/webuntis payload formats
|
|
- Established best practices and versioning strategy
|