# Infoscreen Client - Display Manager Digital signage system for Raspberry Pi that displays presentations, videos, and web content in kiosk mode. Centrally managed via MQTT with automatic client discovery and heartbeat monitoring. ## 🎯 Key Features - **Automatic Presentation Display** - PPTX files converted to PDF and displayed with Impressive - **Auto-Advance Slideshows** - Configurable timing for automatic slide progression - **Loop Mode** - Presentations can loop infinitely or quit after last slide - **MQTT Integration** - Real-time event management from central server - **Group Management** - Organize clients into groups for targeted content - **Heartbeat Monitoring** - Regular status updates and screenshot dashboard - **Multi-Content Support** - Presentations, videos, and web pages - **Kiosk Mode** - Full-screen display with automatic startup ## πŸ“‹ System Requirements ### Hardware - Raspberry Pi 4/5 (or compatible) - HDMI display - Network connectivity (WiFi or Ethernet) - SSD storage recommended ### Software - Raspberry Pi OS (Bookworm or newer) - Python 3.x - LibreOffice (for PPTXβ†’PDF conversion) - Impressive (PDF presenter with auto-advance) - Chromium browser (for web content) - VLC or MPV (for video playback) ## πŸš€ Quick Start ### 1. Installation ```bash # Clone repository cd ~/ git clone infoscreen-dev cd infoscreen-dev # Install system dependencies sudo apt-get update sudo apt-get install -y \ python3 python3-pip python3-venv \ libreoffice impressive \ chromium-browser vlc # Create Python virtual environment python3 -m venv venv source venv/bin/activate # Install Python dependencies pip install -r src/requirements.txt ``` ### 2. Configuration Create `.env` file in project root: ```bash # Environment ENV=production LOG_LEVEL=INFO # MQTT Configuration MQTT_BROKER=192.168.1.100 MQTT_PORT=1883 MQTT_BROKER_FALLBACKS=192.168.1.101,192.168.1.102 # Timing (seconds) HEARTBEAT_INTERVAL=30 SCREENSHOT_INTERVAL=60 DISPLAY_CHECK_INTERVAL=5 # 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 # http or https FILE_SERVER_SCHEME=http # FILE_SERVER_BASE_URL= # optional full override, e.g., http://192.168.1.100:8000 ``` ### 3. Start Services ```bash # Start MQTT client (handles events, heartbeat, discovery) cd ~/infoscreen-dev/src python3 simclient.py # In another terminal: Start Display Manager cd ~/infoscreen-dev/src python3 display_manager.py ``` Or use the startup script: ```bash ./scripts/start-display-manager.sh ``` ## πŸ“Š Presentation System ### How It Works The system uses **Impressive** as the PDF presenter with native auto-advance and loop support: 1. **PPTX files** are automatically converted to PDF using LibreOffice headless 2. **PDF files** are displayed directly with Impressive 3. **Auto-advance** uses Impressive's built-in `--auto` parameter 4. **Loop mode** uses Impressive's `--wrap` parameter (infinite loop) 5. **Auto-quit** uses Impressive's `--autoquit` parameter (exit after last slide) ### Event JSON Format #### Looping Presentation (Typical for Events) ```json { "id": "event_123", "start": "2025-10-01 14:00:00", "end": "2025-10-01 16:00:00", "presentation": { "files": [ { "name": "slides.pptx", "url": "https://server/files/slides.pptx" } ], "auto_advance": true, "slide_interval": 10, "loop": true } } ``` **Result:** Slides advance every 10 seconds, presentation loops infinitely until event ends. #### Single Playthrough ```json { "presentation": { "files": [{"name": "welcome.pptx"}], "auto_advance": true, "slide_interval": 5, "loop": false } } ``` **Result:** Slides advance every 5 seconds, exits after last slide. ### Presentation 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 | ### Scheduler-Specific Fields The scheduler may send additional fields that are preserved in `current_event.json`: | Field | Type | Description | |-------|------|-------------| | `page_progress` | boolean | Show overall progress bar in presentation (Impressive `--page-progress`). Can be provided at `presentation.page_progress` (preferred) or top-level. | | `auto_progress` | boolean | Show per-page auto-advance countdown (Impressive `--auto-progress`). Can be provided at `presentation.auto_progress` (preferred) or top-level. | | `occurrence_of_id` | integer | Original event ID for recurring events | | `recurrence_rule` | string | iCal recurrence rule (RRULE format) | | `recurrence_end` | string | End date for recurring events | **Note:** All fields from the scheduler are automatically preserved when events are stored in `current_event.json`. The client does not filter or modify scheduler-specific metadata. #### Progress Bar Display When using Impressive PDF presenter: - `page_progress: true` - Shows a progress bar at the bottom indicating position in the presentation - `auto_progress: true` - Shows a countdown progress bar for each slide during auto-advance - Both options can be enabled simultaneously for maximum visual feedback ## πŸŽ₯ Video Events ```json { "video": { "url": "https://server/videos/intro.mp4", "loop": true, "autoplay": true, "volume": 0.8 } } ``` Notes: - The Display Manager prefers `python-vlc` (libvlc) when available. This gives programmatic control over playback (autoplay, loop, volume) and ensures the player is cleanly stopped and released when events end. - Supported video event fields: - `url` (string): HTTP/HTTPS or streaming URL. URLs using the placeholder host `server` are rewritten to the configured file server (see File/API Server configuration). - `autoplay` (boolean): start playback automatically when the event becomes active (default: true). - `loop` (boolean): loop playback indefinitely. - `volume` (float): 0.0–1.0 (mapped internally to VLC's 0–100 volume scale). - If `python-vlc` is not installed, the Display Manager will fall back to launching the external `vlc` binary. - The manager attempts to make the player window fullscreen and remove window decorations. For a truly panel-free fullscreen (no taskbar), run the Display Manager inside a minimal kiosk X session or a dedicated user session without a desktop panel (see the kiosk notes below). ## 🌐 Web Events ```json { "web": { "url": "https://dashboard.example.com" } } ``` Opens webpage in Chromium kiosk mode (fullscreen, no UI). ## πŸ—‚οΈ Project Structure ``` infoscreen-dev/ β”œβ”€β”€ .env # Environment configuration β”œβ”€β”€ README.md # This file β”œβ”€β”€ IMPRESSIVE_INTEGRATION.md # Detailed presentation system docs β”œβ”€β”€ src/ β”‚ β”œβ”€β”€ simclient.py # MQTT client (events, heartbeat, discovery) β”‚ β”œβ”€β”€ display_manager.py # Display controller (manages applications) β”‚ β”œβ”€β”€ requirements.txt # Python dependencies β”‚ β”œβ”€β”€ current_event.json # Current active event (runtime) β”‚ β”œβ”€β”€ config/ # Persistent client data β”‚ β”‚ β”œβ”€β”€ client_uuid.txt β”‚ β”‚ └── last_group_id.txt β”‚ β”œβ”€β”€ presentation/ # Downloaded presentation files β”‚ └── screenshots/ # Dashboard screenshots β”œβ”€β”€ scripts/ β”‚ β”œβ”€β”€ start-dev.sh # Start development client β”‚ β”œβ”€β”€ start-display-manager.sh # Start Display Manager β”‚ β”œβ”€β”€ test-display-manager.sh # Interactive testing β”‚ β”œβ”€β”€ test-impressive.sh # Test Impressive (auto-quit mode) β”‚ β”œβ”€β”€ test-impressive-loop.sh # Test Impressive (loop mode) β”‚ β”œβ”€β”€ test-mqtt.sh # Test MQTT connectivity β”‚ β”œβ”€β”€ test-screenshot.sh # Test screenshot capture β”‚ β”œβ”€β”€ test-utc-timestamps.sh # Test event timing β”‚ └── present-pdf-auto-advance.sh # PDF presentation wrapper └── logs/ # Application logs ``` ## πŸ§ͺ Testing ### Test Display Manager ```bash ./scripts/test-display-manager.sh ``` Interactive menu for testing: - Check Display Manager status - Create test events (presentation, video, webpage) - View active processes - Cycle through different event types ### Test Impressive Presentation **Single playthrough (auto-quit):** ```bash ./scripts/test-impressive.sh ``` **Loop mode (infinite):** ```bash ./scripts/test-impressive-loop.sh ``` ### Test MQTT Communication ```bash ./scripts/test-mqtt.sh ``` Verifies MQTT broker connectivity and topics. ### Test Screenshot Capture ```bash ./scripts/test-screenshot.sh ``` Captures test screenshot for dashboard monitoring. ## πŸ”§ Configuration Details ### Environment Variables #### Environment - `ENV` - `development` or `production` - `DEBUG_MODE` - Enable debug output (1=on, 0=off) - `LOG_LEVEL` - `DEBUG`, `INFO`, `WARNING`, `ERROR` #### MQTT - `MQTT_BROKER` - Primary MQTT broker IP/hostname - `MQTT_PORT` - MQTT port (default: 1883) - `MQTT_BROKER_FALLBACKS` - Comma-separated fallback brokers - `MQTT_USERNAME` - Optional authentication - `MQTT_PASSWORD` - Optional authentication #### Timing - `HEARTBEAT_INTERVAL` - Status update frequency (seconds) - `SCREENSHOT_INTERVAL` - Dashboard screenshot frequency (seconds) - `DISPLAY_CHECK_INTERVAL` - Event check frequency (seconds) #### File/API Server - `FILE_SERVER_HOST` - Host/IP of the file server; defaults to `MQTT_BROKER` when empty - `FILE_SERVER_PORT` - Port of the file server (default: 8000) - `FILE_SERVER_SCHEME` - `http` or `https` (default: http) - `FILE_SERVER_BASE_URL` - Optional full override, e.g., `https://api.example.com:443` ### File Server URL Resolution - The MQTT client (`src/simclient.py`) downloads presentation files listed in events. - If an event contains URLs with host `server` (e.g., `http://server:8000/...`) or a missing host, the client rewrites them 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 this behavior using the `.env` variables above; `FILE_SERVER_BASE_URL` takes precedence over the individual host/port/scheme. - Tip: When editing `.env`, keep comments after a space and `#` so values stay clean. ### MQTT Topics #### Client β†’ Server - `infoscreen/discovery` - Initial client announcement - `infoscreen/{client_id}/heartbeat` - Regular status updates - `infoscreen/{client_id}/dashboard` - Screenshot images (base64) #### Server β†’ Client - `infoscreen/{client_id}/discovery_ack` - Server response with client ID - `infoscreen/{client_id}/group_id` - Group assignment - `infoscreen/events/{group_id}` - Event commands for group ### Client Identification **Hardware Token:** SHA256 hash of: - CPU serial number - MAC addresses (all network interfaces) **Persistent UUID:** Stored in `src/config/client_uuid.txt` **Group Membership:** Stored in `src/config/last_group_id.txt` ## πŸ” Troubleshooting ### Display Manager doesn't start presentations **Check Impressive installation:** ```bash which impressive # If not found: sudo apt-get install impressive ``` **Check LibreOffice installation:** ```bash which libreoffice # If not found: sudo apt-get install libreoffice ``` **Check logs:** ```bash tail -f logs/display_manager.log ``` ### Presentations don't convert from PPTX **Verify LibreOffice headless:** ```bash libreoffice --headless --convert-to pdf --outdir /tmp presentation.pptx ls -l /tmp/*.pdf ``` **Check disk space:** ```bash df -h ``` ### Slides don't auto-advance **Verify event JSON:** - `auto_advance: true` is set - `slide_interval` is specified (default: 10) **Test Impressive directly:** ```bash ./scripts/test-impressive.sh ``` ### Presentation doesn't loop **Verify event JSON:** - `loop: true` is set **Test loop mode:** ```bash ./scripts/test-impressive-loop.sh ``` ### File downloads fail Symptoms: - `Failed to resolve 'server'` or `NameResolutionError` when downloading files - `Invalid URL 'http # http or https://...'` in `logs/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 over host/port/scheme - After changing `.env`, restart the simclient process Expected healthy log sequence: - `Lade Datei herunter von: http://:8000/...` - Followed by `"GET /... HTTP/1.1" 200` and `Datei erfolgreich heruntergeladen:` ### VLC hardware decode / renderer issues If you see messages like: ``` [h264_v4l2m2m @ ...] Could not find a valid device [h264_v4l2m2m @ ...] can't configure decoder [... ] avcodec decoder error: cannot start codec (h264_v4l2m2m) ``` that indicates libVLC / ffmpeg attempted to use the platform V4L2 M2M hardware decoder but the kernel/device isn't available. Options to resolve: - Enable the V4L2 M2M codec driver on the system (platform-specific; on Raspberry Pi ensure correct kernel/firmware and codec modules are loaded). Check `v4l2-ctl --list-devices` and `ls /dev/video*` after installing `v4l-utils`. - Disable hardware decoding so libVLC/ffmpeg uses software decoding (reliable but higher CPU). You can test this by launching the `vlc` binary with: ```bash vlc --avcodec-hw=none 'http://' ``` Or modify `src/display_manager.py` to create the libVLC instance with software-decoding forced: ```python instance = vlc.Instance('--avcodec-hw=none', '--no-video-title-show', '--no-video-deco') ``` This is the fastest workaround if hardware decode is not required or not available on the device. ### MQTT connection issues **Test broker connectivity:** ```bash ./scripts/test-mqtt.sh ``` **Check broker status:** ```bash # On server sudo systemctl status mosquitto ``` **Try fallback brokers:** Edit `.env` and add `MQTT_BROKER_FALLBACKS` ### Screenshots not uploading **Test screenshot capture:** ```bash ./scripts/test-screenshot.sh ls -l screenshots/ ``` **Check DISPLAY variable:** ```bash echo $DISPLAY # Should be :0 ``` ## πŸ“š Documentation - **IMPRESSIVE_INTEGRATION.md** - Detailed presentation system documentation - **src/DISPLAY_MANAGER.md** - Display Manager architecture - **src/IMPLEMENTATION_SUMMARY.md** - Implementation overview - **src/README.md** - MQTT client documentation ## πŸ” Security - Hardware-based client identification (non-spoofable) - Configurable MQTT authentication - Local-only file storage - No sensitive data in logs ## 🚒 Production Deployment ### Systemd Service Create `/etc/systemd/system/infoscreen-display.service`: ```ini [Unit] Description=Infoscreen Display Manager After=network.target [Service] Type=simple User=olafn WorkingDirectory=/home/olafn/infoscreen-dev/src Environment="DISPLAY=:0" Environment="XAUTHORITY=/home/olafn/.Xauthority" ExecStart=/home/olafn/infoscreen-dev/venv/bin/python3 /home/olafn/infoscreen-dev/src/display_manager.py Restart=always RestartSec=10 [Install] WantedBy=multi-user.target ``` Enable and start: ```bash sudo systemctl daemon-reload sudo systemctl enable infoscreen-display sudo systemctl start infoscreen-display sudo systemctl status infoscreen-display ``` ### Auto-start on Boot Both services (simclient.py and display_manager.py) should start automatically: 1. **simclient.py** - MQTT communication, event management 2. **display_manager.py** - Display application controller Create similar systemd service for simclient.py. ### Docker Deployment (Alternative) ```bash docker-compose -f src/docker-compose.production.yml up -d ``` See `src/CONTAINER_TRANSITION.md` for details. ## πŸ“ Development ### Development Mode Set in `.env`: ```bash ENV=development DEBUG_MODE=1 LOG_LEVEL=DEBUG HEARTBEAT_INTERVAL=10 ``` ### Start Development Client ```bash ./scripts/start-dev.sh ``` ### View Logs ```bash # Display Manager tail -f logs/display_manager.log # MQTT Client tail -f logs/simclient.log # Both tail -f logs/*.log ``` ## 🀝 Contributing 1. Test changes with `./scripts/test-display-manager.sh` 2. Verify MQTT communication with `./scripts/test-mqtt.sh` 3. Update documentation 4. Submit pull request ## πŸ“„ License [Add your license here] ## πŸ†˜ Support For issues or questions: 1. Check logs in `logs/` directory 2. Review troubleshooting section 3. Test individual components with test scripts 4. Check MQTT broker connectivity --- **Last Updated:** October 2025 **Status:** βœ… Production Ready **Tested On:** Raspberry Pi 5, Raspberry Pi OS (Bookworm) ## Recent changes (Oct 2025) The following notable changes were added after the previous release and are included in this branch: - Event handling: support for scheduler-provided `event_type` values (new types: `presentation`, `webuntis`, `webpage`, `website`). The display manager now prefers `event_type` when selecting which renderer to start. - Web display: Chromium is launched in kiosk mode for web events. `website` events (scheduler) and legacy `web` keys are both supported and normalized. - Auto-scroll feature: automatic scrolling for long websites implemented. Two mechanisms are available: - CDP injection: The display manager attempts to inject a small auto-scroll script via Chrome DevTools Protocol (DevTools websocket) when possible (uses `websocket-client` and `requests`). Default injection duration: 60s. - Extension fallback: When DevTools websocket handshakes are blocked (403), a tiny local Chrome extension (`src/chrome_autoscroll`) is loaded via `--load-extension` to run a content script that performs the auto-scroll reliably. - Autoscroll enabled only for scheduler events with `event_type: "website"` (not for general `web` or `webpage`). The extension and CDP injection are only used when autoscroll is requested for that event type. - New test utilities: - `scripts/test_cdp.py` β€” quick DevTools JSON listing + Runtime.evaluate tester - `scripts/test_cdp_origins.py` β€” tries several Origin headers to diagnose 403 handshakes - Dependencies: `src/requirements.txt` updated to include `websocket-client` (used by the CDP injector). - Small refactors and improved logging in `src/display_manager.py` to make event dispatch and browser injection more robust. If you rely on autoscroll in production, review the security considerations around `--remote-debugging-port` (DevTools) and prefer the extension fallback if your Chromium build enforces strict websocket Origin policies.