README: document dev-mode CEC auto-disable; add testing notes; set CEC_POWER_OFF_WAIT=5 Copilot instructions: add dev-mode CEC behavior and test script guidance test-hdmi-cec.sh: respect ENV=development (skip option 5), explicit .env load, ASCII output, 30s wait display_manager: remove emoji from logs to avoid Unicode errors
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
- HDMI-CEC TV Control - Automatic TV power on/off based on event scheduling
- 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)
- CEC Utils (for HDMI-CEC TV control - optional)
🚀 Quick Start
1. Installation
# Clone repository
cd ~/
git clone <repository-url> 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 \
cec-utils
# 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:
# 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
# HDMI-CEC TV Control (optional)
CEC_ENABLED=true # Enable automatic TV power control (auto-disabled in development)
CEC_DEVICE=0 # Target device (0 recommended for TV)
CEC_TURN_OFF_DELAY=30 # Seconds to wait before turning off TV
CEC_POWER_ON_WAIT=5 # Seconds to wait after power ON (for TV boot)
CEC_POWER_OFF_WAIT=5 # Seconds to wait after power OFF (increase for slower TVs)
3. Start Services
# 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:
./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:
- PPTX files are automatically converted to PDF using LibreOffice headless
- PDF files are displayed directly with Impressive
- Auto-advance uses Impressive's built-in
--autoparameter - Loop mode uses Impressive's
--wrapparameter (infinite loop) - Auto-quit uses Impressive's
--autoquitparameter (exit after last slide)
Event JSON Format
Looping Presentation (Typical for Events)
{
"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
{
"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 presentationauto_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
{
"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 hostserverare 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-vlcis not installed, the Display Manager will fall back to launching the externalvlcbinary. - 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
{
"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
./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):
./scripts/test-impressive.sh
Loop mode (infinite):
./scripts/test-impressive-loop.sh
Test MQTT Communication
./scripts/test-mqtt.sh
Verifies MQTT broker connectivity and topics.
Test Screenshot Capture
./scripts/test-screenshot.sh
Captures test screenshot for dashboard monitoring.
🔧 Configuration Details
Environment Variables
Environment
ENV-developmentorproductionDEBUG_MODE- Enable debug output (1=on, 0=off)LOG_LEVEL-DEBUG,INFO,WARNING,ERROR
MQTT
MQTT_BROKER- Primary MQTT broker IP/hostnameMQTT_PORT- MQTT port (default: 1883)MQTT_BROKER_FALLBACKS- Comma-separated fallback brokersMQTT_USERNAME- Optional authenticationMQTT_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 toMQTT_BROKERwhen emptyFILE_SERVER_PORT- Port of the file server (default: 8000)FILE_SERVER_SCHEME-httporhttps(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 port8000and schemehttp. - You can override this behavior using the
.envvariables above;FILE_SERVER_BASE_URLtakes 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 announcementinfoscreen/{client_id}/heartbeat- Regular status updatesinfoscreen/{client_id}/dashboard- Screenshot images (base64)
Server → Client
infoscreen/{client_id}/discovery_ack- Server response with client IDinfoscreen/{client_id}/group_id- Group assignmentinfoscreen/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:
which impressive
# If not found: sudo apt-get install impressive
Check LibreOffice installation:
which libreoffice
# If not found: sudo apt-get install libreoffice
Check logs:
tail -f logs/display_manager.log
Presentations don't convert from PPTX
Verify LibreOffice headless:
libreoffice --headless --convert-to pdf --outdir /tmp presentation.pptx
ls -l /tmp/*.pdf
Check disk space:
df -h
Slides don't auto-advance
Verify event JSON:
auto_advance: trueis setslide_intervalis specified (default: 10)
Test Impressive directly:
./scripts/test-impressive.sh
Presentation doesn't loop
Verify event JSON:
loop: trueis set
Test loop mode:
./scripts/test-impressive-loop.sh
File downloads fail
Symptoms:
Failed to resolve 'server'orNameResolutionErrorwhen downloading filesInvalid URL 'http # http or https://...'inlogs/simclient.log
What to check:
- Look for lines like
Lade Datei herunter von:inlogs/simclient.logto see the effective URL used - Ensure the URL host is the MQTT broker IP (or your configured file server), not
server - Verify
.envvalues don’t include inline comments as part of the value (e.g., keepFILE_SERVER_SCHEME=httpon its own line)
Fixes:
- If your API is on the same host as the broker: leave
FILE_SERVER_HOSTempty (defaults toMQTT_BROKER), keepFILE_SERVER_PORT=8000, and setFILE_SERVER_SCHEME=httporhttps - 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://<broker-ip>:8000/...- Followed by
"GET /... HTTP/1.1" 200andDatei 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-devicesandls /dev/video*after installingv4l-utils. - Disable hardware decoding so libVLC/ffmpeg uses software decoding (reliable but higher CPU). You can test this by launching the
vlcbinary with:
vlc --avcodec-hw=none 'http://<your-video-url>'
Or modify src/display_manager.py to create the libVLC instance with software-decoding forced:
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:
./scripts/test-mqtt.sh
Check broker status:
# On server
sudo systemctl status mosquitto
Try fallback brokers:
Edit .env and add MQTT_BROKER_FALLBACKS
Screenshots not uploading
Test screenshot capture:
./scripts/test-screenshot.sh
ls -l screenshots/
Check DISPLAY variable:
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:
[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:
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:
- simclient.py - MQTT communication, event management
- display_manager.py - Display application controller
Create similar systemd service for simclient.py.
Docker Deployment (Alternative)
docker-compose -f src/docker-compose.production.yml up -d
See src/CONTAINER_TRANSITION.md for details.
📝 Development
Development Mode
Set in .env:
ENV=development
DEBUG_MODE=1
LOG_LEVEL=DEBUG
HEARTBEAT_INTERVAL=10
Start Development Client
./scripts/start-dev.sh
View Logs
# Display Manager
tail -f logs/display_manager.log
# MQTT Client
tail -f logs/simclient.log
# Both
tail -f logs/*.log
📺 HDMI-CEC TV Control
The system includes automatic TV power control via HDMI-CEC. The TV turns on when events start and turns off (with delay) when no events are active.
Development mode behavior
- When
ENV=development, HDMI-CEC is automatically disabled by the Display Manager to avoid constantly switching the TV during development. - The test script
scripts/test-hdmi-cec.shalso respects this: menu option 5 (Display Manager CEC integration) will detect development mode and skip the integration test. Manual options (1–4) still work for direct cec-client testing.
To test CEC end-to-end, temporarily set ENV=production in .env and restart the Display Manager, or use the manual commands in the test script.
Quick Setup
# Install CEC utilities
sudo apt-get install cec-utils
# Test CEC connection
echo "scan" | cec-client -s -d 1
# Configure in .env
CEC_ENABLED=true
CEC_DEVICE=0 # Use 0 for best performance
CEC_TURN_OFF_DELAY=30
CEC_POWER_ON_WAIT=5 # Adjust if TV is slow to boot
CEC_POWER_OFF_WAIT=2
Features
- Auto Power On: TV turns on when event starts
- Auto Power Off: TV turns off after configurable delay when events end
- Smart Switching: TV stays on when switching between events
- Configurable Delay: Prevent rapid on/off cycles
Testing
# Interactive test menu
./scripts/test-hdmi-cec.sh
# Note: In development mode, option 5 (integration test) is skipped on purpose.
# Use options 1–4 for manual commands, or set ENV=production to run the integration test.
# Manual commands
echo "on 0" | cec-client -s -d 1 # Turn on
echo "standby 0" | cec-client -s -d 1 # Turn off
echo "pow 0" | cec-client -s -d 1 # Check status
Documentation
See HDMI_CEC_SETUP.md for complete documentation including:
- Detailed setup instructions
- Troubleshooting guide
- TV compatibility information
- Advanced configuration options
🤝 Contributing
- Test changes with
./scripts/test-display-manager.sh - Verify MQTT communication with
./scripts/test-mqtt.sh - Update documentation
- Submit pull request
📄 License
[Add your license here]
🆘 Support
For issues or questions:
- Check logs in
logs/directory - Review troubleshooting section
- Test individual components with test scripts
- 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_typevalues (new types:presentation,webuntis,webpage,website). The display manager now prefersevent_typewhen selecting which renderer to start. - Web display: Chromium is launched in kiosk mode for web events.
websiteevents (scheduler) and legacywebkeys 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-clientandrequests). 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-extensionto run a content script that performs the auto-scroll reliably.
- CDP injection: The display manager attempts to inject a small auto-scroll script via Chrome DevTools Protocol (DevTools websocket) when possible (uses
- Autoscroll enabled only for scheduler events with
event_type: "website"(not for generalweborwebpage). 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 testerscripts/test_cdp_origins.py— tries several Origin headers to diagnose 403 handshakes
- Dependencies:
src/requirements.txtupdated to includewebsocket-client(used by the CDP injector). - Small refactors and improved logging in
src/display_manager.pyto 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.