Files
infoscreen-dev/README.md

613 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 <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
# 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.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
```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 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:
```bash
vlc --avcodec-hw=none 'http://<your-video-url>'
```
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.