RobbStarkAustria 65d7b99198 Improve MQTT resilience, clarify behavior, and minor UX
Switch to Paho v2 callbacks; add loop_start() and reconnect_delay_set() for auto-reconnect
Rework on_connect/on_disconnect to v2 signatures; handle session_present and reconnection flows
Gate heartbeats with client.is_connected() and add short retry on rc=4 (NO_CONN)
Re-send discovery after reconnect; ensure re-subscription to all topics
Add startup terminal message with ISO timestamp in simclient.py
Docs: update README and Copilot instructions with reconnection/heartbeat guidance and benign rc=4 notes
2025-11-30 09:20:48 +01:00

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 - Server renders PPTX to PDF; client displays PDFs 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
  • Impressive (PDF presenter with auto-advance)
  • (Optional legacy) LibreOffice ONLY if older workflow still sends raw PPTX; otherwise not required
  • 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:

  1. PPTX files are converted to PDF server-side (client receives ready-made PDFs)
  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)

{
  "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 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

{
  "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.01.0 (mapped internally to VLC's 0100 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

{
  "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 - 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:

which impressive
# If not found: sudo apt-get install impressive

Legacy LibreOffice (only if still receiving raw PPTX):

which libreoffice
# Optional install (legacy only): sudo apt-get install libreoffice

Check logs:

tail -f logs/display_manager.log

Legacy PPTX Conversion (Deprecated)

Server performs all PPTX→PDF rendering. Use these commands only if supporting an older workflow still sending PPTX:

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: true is set
  • slide_interval is specified (default: 10)

Test Impressive directly:

./scripts/test-impressive.sh

Presentation doesn't loop

Verify event JSON:

  • loop: true is set

Test loop mode:

./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 dont 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://<broker-ip>: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:
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

Client auto-reconnect and heartbeat behavior (Nov 2025)

  • The MQTT client now uses Paho v2 callbacks and loop_start() for automatic reconnection with exponential backoff.
  • All topic subscriptions are restored in on_connect and a discovery message is re-sent on reconnect to re-register the client.
  • Heartbeats are sent only when connected; if publish occurs during a brief reconnect window, Paho may return rc=4 (NO_CONN). The client performs a short retry and logs the outcome.
  • Occasional Heartbeat publish failed with code: 4 after broker restart or transient network hiccups is expected and not dangerous. It indicates "not connected at this instant"; the next heartbeat typically succeeds.
  • When to investigate: repeated rc=4 with no succeeding "Heartbeat sent" entries over multiple intervals.

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:

  1. simclient.py - MQTT communication, event management
  2. 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.sh also respects this: menu option 5 (Display Manager CEC integration) will detect development mode and skip the integration test. Manual options (14) 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 14 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

  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.

Description
No description provided
Readme 905 KiB
Languages
Python 70.3%
Shell 29.4%
JavaScript 0.3%