- switch dashboard payload to grouped schema v2.0 in simclient - support immediate event-triggered screenshot sends via meta.json signaling - update README and copilot instructions to document v2 payload and trigger behavior - update migration checklist to reflect completed client/server rollout
596 lines
33 KiB
Markdown
596 lines
33 KiB
Markdown
# Copilot Instructions - Infoscreen Client
|
||
|
||
## Quick Start for AI Assistants
|
||
|
||
### Critical Rules
|
||
- ✅ **ALWAYS use Impressive** for PDF presentations (has native auto-advance/loop)
|
||
- ❌ **NEVER suggest xdotool** approaches (failed on Raspberry Pi due to focus issues)
|
||
- ❌ **NEVER suggest video conversion** (adds complexity, had black screen issues)
|
||
- ✅ **Virtual environment MUST have** pygame + pillow (required for Impressive)
|
||
- ✅ **Client-side resize/compress** screenshots before MQTT transmission
|
||
- ✅ **Server renders PPTX → PDF via Gotenberg** (client only displays PDFs, no LibreOffice needed)
|
||
- ✅ **Keep screenshot consent notice in docs** when describing dashboard screenshot feature
|
||
- ✅ **Event-start/event-stop screenshots must preserve metadata** - See SCREENSHOT_MQTT_FIX.md for critical race condition that was fixed
|
||
- ✅ **Screenshot updates must keep `latest.jpg` and `meta.json` in sync** (simclient prefers `latest.jpg`)
|
||
- ✅ **Dashboard payload uses grouped v2 schema** (`message/content/runtime/metadata`, `schema_version="2.0"`)
|
||
- ✅ **Event-triggered screenshots**: `display_manager` arms a `threading.Timer` after start/stop, captures, writes `meta.json` with `send_immediately=true`; simclient fires within ≤1s
|
||
- ✅ **Payload assembly is centralized** in `_build_dashboard_payload()` — do not build dashboard JSON at call sites
|
||
|
||
### Key Files & Locations
|
||
- **Display logic**: `src/display_manager.py` (controls presentations/video/web)
|
||
- **MQTT client**: `src/simclient.py` (event management, heartbeat, discovery)
|
||
- **Runtime state**: `src/current_event.json` (current active event)
|
||
- **Process health bridge**: `src/current_process_health.json` (display_manager -> simclient)
|
||
- **Config**: `src/config/client_uuid.txt`, `src/config/last_group_id.txt`, `.env`
|
||
- **Logs**: `logs/display_manager.log`, `logs/simclient.log`, `logs/monitoring.log`
|
||
- **Screenshots**: `src/screenshots/` (shared volume between processes)
|
||
|
||
### Common Tasks Quick Reference
|
||
| Task | File | Key Method/Section |
|
||
|------|------|-------------------|
|
||
| Add event type | `display_manager.py` | `start_display_for_event()` |
|
||
| Modify presentation | `display_manager.py` | `start_presentation()` |
|
||
| Modify process monitoring | `display_manager.py` | `ProcessHealthState`, `process_events()` |
|
||
| Publish health/log topics | `simclient.py` | `read_health_state()`, `publish_health_message()`, `publish_log_message()` |
|
||
| Change MQTT topics | `simclient.py` | Topic constants/handlers |
|
||
| Update screenshot | `display_manager.py` | `_capture_screenshot()` |
|
||
| File downloads | `simclient.py` | `resolve_file_url()` |
|
||
|
||
---
|
||
|
||
## Project Overview
|
||
**Infoscreen Client** - Digital signage system for Raspberry Pi. Displays presentations, videos, and web content in kiosk mode. Server-managed via MQTT for educational/research environments with multiple displays.
|
||
|
||
**Architecture**: Two-process design
|
||
- `simclient.py` - MQTT communication (container/native)
|
||
- `display_manager.py` - Display control (host OS with X11/Wayland access)
|
||
|
||
## Architecture & Technology Stack
|
||
|
||
### Core Technologies
|
||
- **Python 3.x** - Main application language
|
||
- **MQTT (paho-mqtt)** - Real-time messaging with server
|
||
- **Impressive** - PDF presenter with native auto-advance and loop support
|
||
- **Environment Variables** - Configuration management via `.env` files
|
||
- **JSON** - Data exchange format for events and configuration
|
||
- **Base64** - Screenshot transmission encoding
|
||
- **Threading** - Background services (screenshot monitoring)
|
||
|
||
### System Components
|
||
- **Main Client** (`simclient.py`) - Core MQTT client and event processor
|
||
- **Display Manager** (`display_manager.py`) - Controls display applications (presentations, videos, web)
|
||
- **Discovery System** - Automatic client registration with server
|
||
- **Heartbeat Monitoring** - Regular status updates and keepalive
|
||
- **Event Processing** - Handles presentation/content switching commands
|
||
- **Screenshot Service** - Dashboard monitoring via image capture (captured by display_manager.py, transmitted by simclient.py)
|
||
- **File Management** - Downloads and manages presentation files
|
||
- **Group Management** - Supports organizing clients into groups
|
||
|
||
## Key Features & Functionality
|
||
|
||
### MQTT Communication Patterns
|
||
- **Discovery**: `infoscreen/discovery` → `infoscreen/{client_id}/discovery_ack`
|
||
- **Heartbeat**: Regular `infoscreen/{client_id}/heartbeat` messages
|
||
- **Health**: `infoscreen/{client_id}/health` (event/process/pid/status)
|
||
- **Client logs**: `infoscreen/{client_id}/logs/error|warn` (selective forwarding)
|
||
### MQTT Reconnection & Heartbeat (Nov 2025)
|
||
- The client uses Paho MQTT v2 callback API with `client.loop_start()` and `client.reconnect_delay_set()` to handle automatic reconnection.
|
||
- `on_connect` re-subscribes to all topics (`discovery_ack`, `config`, `group_id`, current group events) and re-sends discovery on reconnect to re-register with the server.
|
||
- Heartbeats are gated by `client.is_connected()` and retry once on `NO_CONN` (rc=4). Occasional rc=4 warnings are normal right after broker restarts or brief network stalls and typically followed by a successful heartbeat.
|
||
- Do not treat single rc=4 heartbeat warnings as failures. Investigate only if multiple consecutive heartbeats fail without recovery.
|
||
- **Dashboard**: Screenshot transmission via `infoscreen/{client_id}/dashboard` (includes base64-encoded screenshot, timestamp, client status, system info)
|
||
- **Group Assignment**: Server sends group via `infoscreen/{client_id}/group_id`
|
||
- **Events**: Content commands via `infoscreen/events/{group_id}`
|
||
|
||
### Event Types Supported
|
||
```json
|
||
{
|
||
"presentation": {
|
||
"files": [{"url": "https://server/file.pptx", "filename": "file.pptx"}],
|
||
"auto_advance": true,
|
||
"slide_interval": 10,
|
||
"loop": true
|
||
},
|
||
"web": {
|
||
"url": "https://example.com"
|
||
},
|
||
"video": {
|
||
"url": "https://server/video.mp4",
|
||
"loop": false,
|
||
"autoplay": true,
|
||
"volume": 0.8
|
||
}
|
||
}
|
||
```
|
||
|
||
### Presentation System (Impressive-Based)
|
||
- **Server-side conversion**: PPTX files are converted to PDF by the server using Gotenberg
|
||
- **Client receives PDFs**: All presentations arrive as pre-rendered PDF files
|
||
- **Direct display**: PDF files are displayed natively with Impressive (no client-side conversion)
|
||
- **Auto-advance**: Native Impressive `--auto` parameter (no xdotool needed)
|
||
- **Loop mode**: Impressive `--wrap` parameter for infinite looping
|
||
- **Auto-quit**: Impressive `--autoquit` parameter to exit after last slide
|
||
- **Virtual Environment**: Uses venv with pygame + pillow for reliable operation
|
||
- **Reliable**: Works consistently on Raspberry Pi without window focus issues
|
||
|
||
### Client Identification
|
||
- **Hardware Token**: SHA256 hash of serial number + MAC addresses
|
||
- **Persistent UUID**: Stored in `config/client_uuid.txt`
|
||
- **Group Membership**: Persistent group assignment in `config/last_group_id.txt`
|
||
|
||
## Directory Structure
|
||
```
|
||
~/infoscreen-dev/
|
||
├── .env # Environment configuration
|
||
├── README.md # Complete project documentation
|
||
├── IMPRESSIVE_INTEGRATION.md # Presentation system details
|
||
├── QUICK_REFERENCE.md # Quick command reference
|
||
├── .github/ # GitHub configuration
|
||
│ └── copilot-instructions.md
|
||
├── src/ # Source code
|
||
│ ├── simclient.py # MQTT client (event management)
|
||
│ ├── display_manager.py # Display controller (Impressive integration)
|
||
│ ├── current_event.json # Current active event (runtime)
|
||
│ ├── config/ # Persistent client data
|
||
│ │ ├── client_uuid.txt
|
||
│ │ └── last_group_id.txt
|
||
│ ├── presentation/ # Downloaded presentation files & PDFs
|
||
│ └── screenshots/ # Screenshot captures for monitoring
|
||
├── scripts/ # Production & testing utilities
|
||
│ ├── start-dev.sh # Start development client
|
||
│ ├── start-display-manager.sh # Start Display Manager
|
||
│ ├── test-display-manager.sh # Interactive testing menu
|
||
│ ├── test-impressive.sh # Test Impressive (auto-quit)
|
||
│ ├── test-impressive-loop.sh # Test Impressive (loop mode)
|
||
│ ├── test-mqtt.sh # MQTT connectivity test
|
||
│ ├── test-screenshot.sh # Screenshot capture test
|
||
│ └── present-pdf-auto-advance.sh # PDF presentation wrapper
|
||
├── logs/ # Application logs
|
||
│ ├── simclient.log
|
||
│ └── display_manager.log
|
||
└── venv/ # Python virtual environment
|
||
```
|
||
|
||
## Configuration & Environment Variables
|
||
|
||
### Development vs Production
|
||
- **Development**: `ENV=development`, verbose logging, frequent heartbeats
|
||
- **Production**: `ENV=production`, minimal logging, longer intervals
|
||
|
||
HDMI-CEC behavior:
|
||
- In development mode (`ENV=development`) the Display Manager automatically disables HDMI-CEC to avoid constantly switching the TV during local testing. The test helper `scripts/test-hdmi-cec.sh` also respects this: option 5 (Display Manager CEC integration) detects dev mode and skips running CEC commands. Manual options (1–4) still work for direct `cec-client` checks.
|
||
|
||
### Key Environment Variables
|
||
```bash
|
||
# Environment
|
||
ENV=development|production
|
||
DEBUG_MODE=1|0
|
||
LOG_LEVEL=DEBUG|INFO|WARNING|ERROR
|
||
|
||
# MQTT Configuration
|
||
MQTT_BROKER=192.168.1.100 # Primary MQTT broker
|
||
MQTT_PORT=1883 # MQTT port
|
||
MQTT_BROKER_FALLBACKS=host1,host2 # Fallback brokers
|
||
|
||
# Timing (seconds)
|
||
HEARTBEAT_INTERVAL=10 # Status update frequency
|
||
SCREENSHOT_INTERVAL=30 # Dashboard screenshot transmission frequency (simclient.py)
|
||
SCREENSHOT_CAPTURE_INTERVAL=30 # Screenshot capture frequency (display_manager.py)
|
||
|
||
# Screenshot Configuration
|
||
SCREENSHOT_MAX_WIDTH=800 # Downscale width (preserves aspect ratio)
|
||
SCREENSHOT_JPEG_QUALITY=70 # JPEG compression quality (1-95)
|
||
SCREENSHOT_MAX_FILES=20 # Number of screenshots to keep (rotation)
|
||
SCREENSHOT_ALWAYS=0 # Force capture even when no display active (testing)
|
||
|
||
# File/API Server (used to download presentation files)
|
||
# Defaults to the same host as MQTT_BROKER, port 8000, scheme http.
|
||
# If incoming event URLs use host 'server' (or are host-less), simclient rewrites them to this server.
|
||
FILE_SERVER_HOST= # optional; if empty, defaults to MQTT_BROKER
|
||
FILE_SERVER_PORT=8000 # default API port
|
||
FILE_SERVER_SCHEME=http # http or https
|
||
# FILE_SERVER_BASE_URL= # optional full override, e.g., http://192.168.1.100:8000
|
||
```
|
||
|
||
### File Server URL Resolution
|
||
- The MQTT client (`simclient.py`) downloads presentation files listed in events.
|
||
- To avoid DNS issues when event URLs use `http://server:8000/...`, the client normalizes such URLs to the configured file server.
|
||
- By default, the file server host is the same as `MQTT_BROKER`, with port `8000` and scheme `http`.
|
||
- You can override behavior using `.env` variables above; `FILE_SERVER_BASE_URL` takes precedence over individual host/port/scheme.
|
||
- Inline comments in `.env` are supported; keep comments after a space and `#` so values stay clean.
|
||
|
||
## Development Patterns & Best Practices
|
||
|
||
### Error Handling
|
||
- Robust MQTT connection with fallbacks and retries
|
||
- Graceful degradation when services unavailable
|
||
- Comprehensive logging with rotating file handlers
|
||
- Exception handling for all external operations
|
||
|
||
### State Management
|
||
- Event state persisted in `current_event.json`
|
||
- Client configuration persisted across restarts
|
||
- Group membership maintained with server synchronization
|
||
- Clean state transitions (delete old events on group changes)
|
||
|
||
### Threading Architecture
|
||
- Main thread: MQTT communication and heartbeat
|
||
- Background thread: Screenshot monitoring service
|
||
- Thread-safe operations for shared resources
|
||
|
||
### File Operations
|
||
- Automatic directory creation for all output paths
|
||
- Safe file operations with proper exception handling
|
||
- Atomic writes for configuration files
|
||
- Automatic cleanup of temporary/outdated files
|
||
|
||
## Development Workflow
|
||
|
||
### Local Development Setup
|
||
1. Clone repository to `~/infoscreen-dev`
|
||
2. Create virtual environment: `python3 -m venv venv`
|
||
3. Install dependencies: `pip install -r src/requirements.txt` (includes pygame + pillow for PDF slideshows)
|
||
4. Configure `.env` file with MQTT broker settings
|
||
5. Use `./scripts/start-dev.sh` for MQTT client or `./scripts/start-display-manager.sh` for display manager
|
||
6. **Important**: Virtual environment must include pygame and pillow for PDF auto-advance to work
|
||
|
||
### Testing Components
|
||
- `./scripts/test-mqtt.sh` - MQTT connectivity
|
||
- `./scripts/test-screenshot.sh` - Screenshot capture
|
||
- `./scripts/test-display-manager.sh` - Interactive testing menu
|
||
- `./scripts/test-impressive.sh` - Test auto-quit presentation mode
|
||
- `./scripts/test-impressive-loop.sh` - Test loop presentation mode
|
||
- `./scripts/test-utc-timestamps.sh` - Event timing validation
|
||
- Manual event testing via mosquitto_pub or test-display-manager.sh
|
||
|
||
### Production Deployment
|
||
- Docker containerization available (`docker-compose.production.yml`)
|
||
- Systemd service integration for auto-start
|
||
- Resource limits and health checks configured
|
||
- Persistent volume mounts for data
|
||
### System Dependencies
|
||
- Python 3.x runtime + virtual environment
|
||
- MQTT broker connectivity
|
||
- Display server: X11 or Wayland (for screenshots)
|
||
- **Impressive** - PDF presenter (primary tool, requires pygame + pillow in venv)
|
||
- **Chromium/Chrome** - Web kiosk mode
|
||
- **VLC** - Video playback (python-vlc preferred, vlc binary fallback)
|
||
- **Screenshot tools**:
|
||
- X11: `scrot` or `import` (ImageMagick) or `xwd`+`convert`
|
||
- Wayland: `grim` or `gnome-screenshot` or `spectacle`
|
||
|
||
**Note:** LibreOffice is NOT required on the client. PPTX→PDF conversion is handled server-side by Gotenberg.
|
||
|
||
### Video Playback (python-vlc)
|
||
- **Preferred**: python-vlc (programmatic control: autoplay, loop, volume)
|
||
- **Fallback**: External vlc binary
|
||
- **Fields**: `url`, `autoplay` (bool), `loop` (bool), `volume` (0.0-1.0 → 0-100)
|
||
- **URL rewriting**: `server` host → configured file server
|
||
- **Fullscreen**: enforced for python-vlc on startup (with short retry toggles); external fallback uses `--fullscreen`
|
||
- **External VLC audio**: `muted=true` (or effective volume 0%) starts with `--no-audio`; otherwise startup loudness is applied via `--gain=<0.00-1.00>`
|
||
- **Runtime volume semantics**: python-vlc supports live updates; external VLC fallback is startup-parameter based
|
||
- **Monitoring PID semantics**: python-vlc runs in-process, so PID is `display_manager.py` runtime PID; external fallback uses external `vlc` PID
|
||
- **HW decode errors**: `h264_v4l2m2m` failures are normal if V4L2 M2M unavailable; use software decode
|
||
- Robust payload parsing with fallbacks
|
||
- Topic-specific message handlers
|
||
- Retained message support where appropriate
|
||
|
||
### Logging & Timestamp Policy (Mar 2026)
|
||
- Client logs are standardized to UTC with `Z` suffix to avoid DST/localtime drift.
|
||
- Applies to `display_manager.log`, `simclient.log`, and `monitoring.log`.
|
||
- MQTT payload timestamps for heartbeat/dashboard/health/log messages are UTC ISO timestamps.
|
||
- Screenshot metadata timestamps included by `simclient.py` are UTC ISO timestamps.
|
||
- Prefer UTC-aware calls (`datetime.now(timezone.utc)`) and UTC log formatters for new code.
|
||
|
||
## Hardware Considerations
|
||
|
||
### Target Platform
|
||
- **Primary**: Raspberry Pi 4/5 with desktop environment
|
||
- **Storage**: SSD recommended for performance
|
||
- **Display**: HDMI output for presentation display
|
||
- **Network**: WiFi or Ethernet connectivity required
|
||
|
||
### System Dependencies
|
||
- Python 3.x runtime
|
||
- Network connectivity for MQTT
|
||
- Display server (X11 or Wayland) for screenshot capture
|
||
- **Impressive** - PDF presenter with auto-advance (primary presentation tool)
|
||
- **pygame** - Required for Impressive (installed in venv)
|
||
- **Pillow/PIL** - Required for Impressive PDF rendering (installed in venv)
|
||
- Chromium/Chrome - Web content display (kiosk mode)
|
||
- VLC or MPV - Video playback
|
||
|
||
**Note:** LibreOffice is NOT needed on the client. The server converts PPTX to PDF using Gotenberg.
|
||
|
||
### Video playback details (python-vlc)
|
||
|
||
- The Display Manager now prefers using python-vlc (libvlc) when available for video playback. This enables programmatic control (autoplay, loop, volume) and cleaner termination/cleanup. If python-vlc is not available, the external `vlc` binary is used as a fallback.
|
||
- Supported video event fields: `url`, `autoplay` (boolean), `loop` (boolean), `volume` (float 0.0-1.0). The manager converts `volume` to VLC's 0-100 scale.
|
||
- External VLC fallback applies audio at startup: `--no-audio` when muted/effective 0%, otherwise `--gain` from effective volume.
|
||
- Live volume adjustments are reliable in python-vlc mode; external VLC fallback uses startup parameters and should be treated as static per launch.
|
||
- URLs using the placeholder host `server` (for example `http://server:8000/...`) are rewritten to the configured file server before playback. The resolution priority is: `FILE_SERVER_BASE_URL` > `FILE_SERVER_HOST` (or `MQTT_BROKER`) + `FILE_SERVER_PORT` + `FILE_SERVER_SCHEME`.
|
||
- Hardware-accelerated decoding errors (e.g., `h264_v4l2m2m`) may appear when the platform does not expose a V4L2 M2M device. To avoid these errors the Display Manager can be configured to disable hw-decoding (see README env var `VLC_HW_ACCEL`). By default the manager will attempt hw-acceleration when libvlc supports it.
|
||
- Fullscreen / kiosk: the manager will attempt to make libVLC windows fullscreen (remove decorations) when using python-vlc, and the README contains recommended system-level kiosk/session setup for a truly panel-free fullscreen experience.
|
||
|
||
|
||
## Security & Privacy
|
||
|
||
### Data Protection
|
||
- Hardware identification via cryptographic hash
|
||
- No sensitive data in plain text logs
|
||
- Local storage of minimal required data only
|
||
- Secure MQTT communication (configurable)
|
||
|
||
### Network Security
|
||
- Configurable MQTT authentication (if broker requires)
|
||
- Firewall-friendly design (outbound connections only)
|
||
- Multiple broker fallback for reliability
|
||
|
||
## Presentation System Architecture
|
||
|
||
### How It Works
|
||
1. **Server-side Conversion** → Server converts PPTX to PDF using Gotenberg
|
||
2. **Event Received** → Client receives event with pre-rendered PDF file reference
|
||
3. **Download PDF** → Client downloads PDF from file server
|
||
4. **Cache PDF** → Downloaded PDF stored in `presentation/` directory
|
||
5. **Display with Impressive** → Launch with venv environment and parameters:
|
||
- `--fullscreen` - Full screen mode
|
||
- `--nooverview` - No slide overview
|
||
- `--auto N` - Auto-advance every N seconds
|
||
- `--wrap` - Loop infinitely (if `loop: true`)
|
||
- `--autoquit` - Exit after last slide (if `loop: false`)
|
||
|
||
### Key Parameters
|
||
| Parameter | Type | Default | Description |
|
||
|-----------|------|---------|-------------|
|
||
| `auto_advance` | boolean | `false` | Enable automatic slide advancement |
|
||
| `slide_interval` | integer | `10` | Seconds between slides |
|
||
| `loop` | boolean | `false` | Loop presentation vs. quit after last slide |
|
||
|
||
### Why Impressive?
|
||
- ✅ **Native auto-advance** - No xdotool or window management hacks
|
||
- ✅ **Built-in loop support** - Reliable `--wrap` parameter
|
||
- ✅ **Works on Raspberry Pi** - No focus/window issues
|
||
- ✅ **Simple integration** - Clean command-line interface
|
||
- ✅ **Maintainable** - ~50 lines of code vs. 200+ with xdotool approaches
|
||
|
||
### Implementation Location
|
||
- **File**: `src/display_manager.py`
|
||
- **Method**: `start_presentation()`
|
||
- **Key Logic**:
|
||
1. Receive event with PDF file reference (server already converted PPTX)
|
||
2. Download PDF file if not cached
|
||
3. Set up virtual environment for Impressive (pygame + pillow)
|
||
4. Build Impressive command with appropriate parameters
|
||
5. Launch process and monitor
|
||
|
||
## Common Development Tasks
|
||
|
||
When working on this codebase:
|
||
|
||
1. **Adding new event types**: Extend the event processing logic in `display_manager.py` → `start_display_for_event()`
|
||
2. **Modifying presentation behavior**: Update `display_manager.py` → `start_presentation()`
|
||
3. **Configuration changes**: Update environment variable parsing and validation
|
||
4. **MQTT topics**: Follow the established `infoscreen/` namespace pattern
|
||
5. **Error handling**: Always include comprehensive logging and graceful fallbacks
|
||
6. **State persistence**: Use the established `config/` directory pattern
|
||
7. **Testing**: Use `./scripts/test-display-manager.sh` for interactive testing
|
||
8. **Presentation testing**: Use `./scripts/test-impressive*.sh` scripts
|
||
9. **File download host resolution**: If the API server differs from the MQTT broker or uses HTTPS, set `FILE_SERVER_*` in `.env` or adjust `resolve_file_url()` in `src/simclient.py`.
|
||
|
||
## Troubleshooting Guidelines
|
||
|
||
### Common Issues
|
||
- **MQTT Connection**: Check broker reachability, try fallback brokers
|
||
- **Screenshots**: Verify display environment and permissions
|
||
- **File Downloads**: Check network connectivity and disk space
|
||
- If event URLs use host `server` and DNS fails, the client rewrites to `MQTT_BROKER` by default.
|
||
- Ensure `MQTT_BROKER` points to the correct server IP; if the API differs, set `FILE_SERVER_HOST` or `FILE_SERVER_BASE_URL`.
|
||
- Match scheme/port via `FILE_SERVER_SCHEME`/`FILE_SERVER_PORT` for HTTPS or non-default ports.
|
||
- **Group Changes**: Monitor log for group assignment messages
|
||
- **Service Startup**: Check systemd logs and environment configuration
|
||
|
||
### Debugging Tools
|
||
- Log files in `logs/simclient.log` and `logs/display_manager.log` with rotation
|
||
- MQTT message monitoring with mosquitto_sub
|
||
- Interactive testing menu: `./scripts/test-display-manager.sh`
|
||
- Component test scripts: `test-impressive*.sh`, `test-mqtt.sh`, etc.
|
||
- Process monitoring: Check for `impressive`, `libreoffice`, `chromium`, `vlc` processes
|
||
|
||
### File download URL troubleshooting
|
||
- Symptoms:
|
||
- `Failed to resolve 'server'` or `NameResolutionError` when downloading files
|
||
- `Invalid URL 'http # http or https://...'` in `simclient.log`
|
||
- What to check:
|
||
- Look for lines like `Lade Datei herunter von:` in `logs/simclient.log` to see the effective URL used
|
||
- Ensure the URL host is the MQTT broker IP (or your configured file server), not `server`
|
||
- Verify `.env` values don’t include inline comments as part of the value (e.g., keep `FILE_SERVER_SCHEME=http` on its own line)
|
||
- Fixes:
|
||
- If your API is on the same host as the broker: leave `FILE_SERVER_HOST` empty (defaults to `MQTT_BROKER`), keep `FILE_SERVER_PORT=8000`, and set `FILE_SERVER_SCHEME=http` or `https`
|
||
- To override fully, set `FILE_SERVER_BASE_URL` (e.g., `http://192.168.1.100:8000`); this takes precedence
|
||
- After changing `.env`, restart the simclient process
|
||
- Expected healthy log sequence:
|
||
- `Lade Datei herunter von: http://<broker-ip>:8000/...`
|
||
- Followed by `"GET /... HTTP/1.1" 200` and `Datei erfolgreich heruntergeladen:`
|
||
|
||
### Screenshot MQTT Transmission Issue (Event-Start/Event-Stop)
|
||
- **Symptom**: Event-triggered screenshots (event_start, event_stop) are NOT appearing on the dashboard, only periodic screenshots transmitted
|
||
- **Root Cause**: Race condition in metadata/file-pointer handling where periodic captures can overwrite event-triggered metadata or `latest.jpg` before simclient processes it (See SCREENSHOT_MQTT_FIX.md for details)
|
||
- **What to check**:
|
||
- Display manager logs show event_start/event_stop screenshots ARE being captured: `Screenshot captured: ... type=event_start`
|
||
- But `meta.json` is stale or `latest.jpg` does not move
|
||
- MQTT heartbeats lack screenshot data at event transitions
|
||
- **How to verify the fix**:
|
||
- Run: `./test-screenshot-meta-fix.sh` should output `[SUCCESS] Event-triggered metadata preserved!`
|
||
- Check display_manager.py: `_write_screenshot_meta()` has protection logic to skip periodic overwrites of event metadata
|
||
- Check display_manager.py: periodic `latest.jpg` updates are also protected when triggered metadata is pending
|
||
- Check simclient.py: `screenshot_service_thread()` logs show pending event-triggered captures being processed immediately
|
||
- **Permanent Fix**: Already applied in display_manager.py and simclient.py. Prevents periodic captures from overwriting pending trigger state and includes stale-trigger self-healing.
|
||
|
||
### Screenshot Capture After Restart (No Active Event)
|
||
- In `ENV=development`, display_manager performs periodic idle captures so dashboard does not appear dead during no-event windows.
|
||
- In `ENV=production`, periodic captures remain event/process-driven unless `SCREENSHOT_ALWAYS=1`.
|
||
- If display_manager is started from non-interactive shells (systemd/nohup/ssh), it now attempts `DISPLAY=:0` and `XAUTHORITY=~/.Xauthority` fallback for X11 capture tools.
|
||
|
||
## Important Notes for AI Assistants
|
||
|
||
### Virtual Environment Requirements (Critical)
|
||
- **pygame and pillow MUST be installed in venv** - Required for Impressive to work
|
||
- **Display manager uses venv context** - Ensures Impressive has access to dependencies
|
||
- **Installation command**: `pip install pygame pillow` (already in requirements.txt)
|
||
- **If pygame missing**: Impressive will fail with "No module named 'pygame'" error
|
||
|
||
### Presentation System
|
||
- **ALWAYS use Impressive** for PDF presentations (primary solution)
|
||
- **DO NOT suggest xdotool approaches** - they failed on Raspberry Pi due to window focus issues
|
||
- **DO NOT suggest video conversion** - adds complexity, had black screen issues
|
||
- **All presentations are PDFs** - server converts PPTX to PDF using Gotenberg
|
||
- **No client-side conversion** - client only displays pre-rendered PDFs
|
||
- **Virtual environment is required** - pygame + pillow must be available for Impressive
|
||
- **Loop mode uses `--wrap`** - not custom scripts or workarounds
|
||
- **Auto-quit uses `--autoquit`** - native Impressive parameter
|
||
|
||
### Testing Approach
|
||
- Use `./scripts/test-display-manager.sh` for interactive testing
|
||
- Use `./scripts/test-impressive-loop.sh` to verify loop functionality
|
||
- Test individual components with specific test scripts
|
||
- Always check logs in `logs/` directory for debugging
|
||
|
||
CEC testing notes:
|
||
- In development mode, the CEC integration path is skipped on purpose. To test end-to-end, either set `ENV=production` temporarily or use the manual options (1–4) in `scripts/test-hdmi-cec.sh`.
|
||
|
||
### Code Changes
|
||
- Display logic is in `src/display_manager.py`, not `simclient.py`
|
||
- MQTT client (`simclient.py`) writes events to `current_event.json`
|
||
- Display Manager reads `current_event.json` and launches appropriate applications
|
||
- Two separate processes: simclient.py (MQTT) + display_manager.py (display control)
|
||
|
||
### Documentation
|
||
- **README.md** - Start here for comprehensive overview
|
||
- **IMPRESSIVE_INTEGRATION.md** - Deep dive into presentation system
|
||
- **QUICK_REFERENCE.md** - Quick commands and examples
|
||
- Source code has extensive comments and logging
|
||
|
||
This system is designed for reliability and ease of maintenance in educational environments with multiple deployed clients. The Impressive-based presentation solution provides native auto-advance and loop support without complex window management hacks.
|
||
|
||
## Screenshot System (Nov 2025)
|
||
|
||
The screenshot capture and transmission system has been implemented with separation of concerns:
|
||
|
||
### Architecture
|
||
- **Capture**: `display_manager.py` captures screenshots in a background thread and writes to shared `screenshots/` directory
|
||
- **Transmission**: `simclient.py` reads latest screenshot from shared directory and transmits via MQTT dashboard topic
|
||
- **Sharing**: Volume-based sharing between display_manager (host OS) and simclient (container)
|
||
|
||
### Capture Strategy (display_manager.py)
|
||
- **Session Detection**: Automatically detects Wayland vs X11 session
|
||
- **Wayland Tools**: Tries `grim`, `gnome-screenshot`, `spectacle` (in order)
|
||
- **X11 Tools**: Tries `scrot`, `import` (ImageMagick), `xwd`+`convert` (in order)
|
||
- **Processing**: Downscales to max width (default 800px), JPEG compresses (default quality 70)
|
||
- **Output**: Creates timestamped files (`screenshot_YYYYMMDD_HHMMSS.jpg`) plus `latest.jpg` symlink
|
||
- **Rotation**: Keeps max N files (default 20), deletes older
|
||
- **Timing**: Production captures when display process is active (unless `SCREENSHOT_ALWAYS=1`); development allows periodic idle captures to keep dashboard fresh
|
||
- **Reliability**: Stale/invalid pending trigger metadata is ignored automatically to avoid lock-up of periodic updates
|
||
- **Event-triggered captures**: `_trigger_event_screenshot(type, delay)` arms a one-shot `threading.Timer` after event start/stop; timer is cancelled and replaced on rapid event switches; default delays: presentation=4s, video=2s, web=5s (env-configurable)
|
||
- **IPC signal file** (`screenshots/meta.json`): written atomically by `display_manager` after each capture; contains `type`, `captured_at`, `file`, `send_immediately`; `send_immediately=true` for event-triggered, `false` for periodic
|
||
|
||
### Transmission Strategy (simclient.py)
|
||
- **Source**: Prefers `screenshots/latest.jpg` if present, falls back to newest timestamped file
|
||
- **Topic**: `infoscreen/{client_id}/dashboard`
|
||
- **Format**: JSON with base64-encoded image data, grouped v2 schema
|
||
- **Schema version**: `"2.0"` (legacy flat fields removed; all fields grouped)
|
||
- **Payload builder**: `_build_dashboard_payload()` in `simclient.py` — single source of truth
|
||
- **Payload Structure** (v2):
|
||
```json
|
||
{
|
||
"message": { "client_id": "UUID", "status": "alive" },
|
||
"content": {
|
||
"screenshot": {
|
||
"filename": "latest.jpg",
|
||
"data": "base64...",
|
||
"timestamp": "ISO datetime",
|
||
"size": 12345
|
||
}
|
||
},
|
||
"runtime": {
|
||
"system_info": { "hostname": "...", "ip": "...", "uptime": 123456.78 },
|
||
"process_health": { "event_type": "...", "process_status": "...", ... }
|
||
},
|
||
"metadata": {
|
||
"schema_version": "2.0",
|
||
"producer": "simclient",
|
||
"published_at": "ISO datetime",
|
||
"capture": {
|
||
"type": "periodic | event_start | event_stop",
|
||
"captured_at": "ISO datetime",
|
||
"age_s": 0.9,
|
||
"triggered": false,
|
||
"send_immediately": false
|
||
},
|
||
"transport": { "topic": "infoscreen/.../dashboard", "qos": 0, "publisher": "simclient" }
|
||
}
|
||
}
|
||
```
|
||
- **Capture types**: `periodic` (interval-based), `event_start` (N seconds after event launch), `event_stop` (1s after process killed)
|
||
- **Triggered send**: `display_manager` sets `send_immediately=true` in `meta.json`; simclient 1-second tick detects and fires within ≤1s
|
||
- **Logging**: `Dashboard published: schema=2.0 type=<type> screenshot=<file> (<bytes>) age=<s>`
|
||
|
||
### Scalability Considerations
|
||
- **Client-side resize/compress**: Reduces bandwidth and broker load (recommended for 50+ clients)
|
||
- **Recommended production settings**: `SCREENSHOT_CAPTURE_INTERVAL=60`, `SCREENSHOT_MAX_WIDTH=800`, `SCREENSHOT_JPEG_QUALITY=60-70`
|
||
- **Future optimization**: Hash-based deduplication to skip identical screenshots
|
||
- **Alternative for large scale**: HTTP storage + MQTT metadata (200+ clients)
|
||
|
||
### Testing
|
||
- Install capture tools: `sudo apt install scrot imagemagick` (X11) or `sudo apt install grim gnome-screenshot` (Wayland)
|
||
- Force capture for testing: `export SCREENSHOT_ALWAYS=1`
|
||
- Check logs: `tail -f logs/display_manager.log logs/simclient.log`
|
||
- Verify files: `ls -lh src/screenshots/`
|
||
|
||
### Troubleshooting
|
||
- **No screenshots**: Check session type in logs, install appropriate tools
|
||
- **Large payloads**: Reduce `SCREENSHOT_MAX_WIDTH` or `SCREENSHOT_JPEG_QUALITY`
|
||
- **Stale screenshots**: Check `latest.jpg` symlink, verify display_manager is running
|
||
- **MQTT errors**: Check dashboard topic logs for publish return codes
|
||
- **Pulse overflow in remote sessions**: warnings like `pulse audio output error: overflow, flushing` can occur with NoMachine/dummy displays; if HDMI playback is stable, treat as environment-related
|
||
- **After restarts**: Ensure both processes are restarted (`simclient.py` and `display_manager.py`) so metadata consumption and capture behavior use the same code version
|
||
### Testing & Troubleshooting
|
||
**Setup:**
|
||
- X11: `sudo apt install scrot imagemagick`
|
||
- Wayland: `sudo apt install grim gnome-screenshot`
|
||
- Force capture: `export SCREENSHOT_ALWAYS=1`
|
||
|
||
**Verify:**
|
||
```bash
|
||
tail -f logs/display_manager.log | grep screenshot # Check capture
|
||
tail -f logs/simclient.log | grep dashboard # Check transmission
|
||
ls -lh src/screenshots/ # Check files
|
||
```
|
||
|
||
**Common Issues:**
|
||
| Issue | Check | Fix |
|
||
|-------|-------|-----|
|
||
| No screenshots | Session type in logs | Install tools for X11/Wayland |
|
||
| Large payloads | File sizes | Reduce `SCREENSHOT_MAX_WIDTH` or `SCREENSHOT_JPEG_QUALITY` |
|
||
| Stale data | `latest.jpg` timestamp | Restart display_manager |
|
||
| MQTT errors | Publish return codes | Check broker connectivity |
|
||
|
||
---
|
||
|
||
## Development Notes
|
||
|
||
### Event Types (Scheduler Integration)
|
||
- **Supported**: `presentation`, `webuntis`, `webpage`, `website`
|
||
- **Auto-scroll**: Only for `event_type: "website"` (CDP injection + Chrome extension fallback)
|
||
- **Display manager**: Uses `event_type` to select renderer when available
|
||
|
||
### HDMI-CEC Behavior
|
||
- **Development mode**: CEC auto-disabled (prevents TV cycling during testing)
|
||
- **Test script**: `test-hdmi-cec.sh` option 5 skips integration test in dev mode
|
||
- **Manual testing**: Options 1-4 work regardless of mode
|
||
|
||
### Code Modification Guidelines
|
||
- **Presentations**: Always use Impressive (`--auto`, `--wrap`, `--autoquit`)
|
||
- **Web autoscroll**: Only for `event_type: "website"`, keep CDP optional
|
||
- **Screenshot changes**: Remember two-process architecture (capture ≠ transmission)
|
||
- **URL resolution**: Handle `server` host placeholder in file/video URLs |