docs: refactor docs structure and tighten assistant instruction policy

shrink root README into a landing page with a docs map and focused contributor guidance
add TV_POWER_RUNBOOK as the canonical TV power rollout and canary runbook
add CHANGELOG and move project history out of README-style docs
refactor src README into a developer-focused guide (architecture, runtime files, MQTT, debugging)
prune redundant older HDMI docs and keep a canonical HDMI_CEC_SETUP path
update copilot instructions to a high-signal policy format with strict anti-shadow-README design rules
align references across docs to current files, scripts, and TV power behavior
This commit is contained in:
RobbStarkAustria
2026-04-01 10:01:58 +02:00
parent fb0980aa88
commit 82f43f75ba
20 changed files with 2228 additions and 2267 deletions

816
README.md
View File

@@ -1,48 +1,29 @@
# Infoscreen Client - Display Manager
# Infoscreen Client
Digital signage system for Raspberry Pi that displays presentations, videos, and web content in kiosk mode. Centrally managed via MQTT with automatic client discovery, heartbeat monitoring, and screenshot-based dashboard monitoring.
Digital signage client for Raspberry Pi that displays presentations, videos, and web content in kiosk mode. It is managed centrally via MQTT and includes HDMI-CEC TV control, screenshot-based dashboard monitoring, and process health reporting.
## 🎯 Key Features
Dashboard screenshots can contain visible on-screen content. Keep that in mind when enabling or documenting remote monitoring.
- **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
- **Client Process Monitoring** - Health-state bridge, crash/restart tracking, and monitoring log
- **Screenshot Dashboard** - Automatic screen capture with Wayland/X11 support, client-side compression
- **Multi-Content Support** - Presentations, videos, and web pages
- **Kiosk Mode** - Full-screen display with automatic startup
## Key Features
## 📋 System Requirements
- Server-side PPTX to PDF rendering; client displays PDFs with Impressive.
- Presentation auto-advance, loop mode, and progress indicators.
- Video playback with `python-vlc` when available and external VLC fallback.
- Web and WebUntis display in kiosk mode.
- HDMI-CEC TV power control with local fallback and MQTT-coordinated power intent.
- MQTT discovery, heartbeat, group assignment, and event delivery.
- Screenshot dashboard with Wayland/X11 capture tool fallbacks.
- Process health bridge between `display_manager.py` and `simclient.py`.
### Hardware
- Raspberry Pi 4/5 (or compatible)
- HDMI display
- Network connectivity (WiFi or Ethernet)
- SSD storage recommended
## Quick Start
### Software
- Raspberry Pi OS (Bookworm or newer)
- Python 3.x
- Impressive (PDF presenter with auto-advance)
- Chromium browser (for web content)
- VLC or MPV (for video playback)
- Screenshot tools: `scrot` or ImageMagick (X11) OR `grim` or `gnome-screenshot` (Wayland)
- CEC Utils (for HDMI-CEC TV control - optional)
## 🚀 Quick Start
### 1. Installation
### 1. Install Dependencies
```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 \
@@ -51,729 +32,204 @@ sudo apt-get install -y \
cec-utils \
scrot imagemagick
# For Wayland systems, install screenshot tools:
# For Wayland systems:
# sudo apt-get install grim gnome-screenshot
# Create Python virtual environment
python3 -m venv venv
source venv/bin/activate
# Install Python dependencies
pip install -r src/requirements.txt
```
### 2. Configuration
### 2. Configure `.env`
Create `.env` file in project root (or copy from `.env.template`):
Copy `.env.template` to `.env` and set at least:
```bash
# Screenshot capture behavior
SCREENSHOT_ALWAYS=0 # Set to 1 for testing (forces capture even without active display)
ENV=production
DEBUG_MODE=0
LOG_LEVEL=INFO
# Environment
ENV=production # development | production (CEC disabled in development)
DEBUG_MODE=0 # 1 to enable debug mode
LOG_LEVEL=INFO # DEBUG | INFO | WARNING | ERROR
# MQTT Configuration
MQTT_BROKER=192.168.1.100 # Your MQTT broker IP/hostname
MQTT_BROKER=192.168.1.100
MQTT_PORT=1883
# Timing (seconds)
HEARTBEAT_INTERVAL=60 # How often client sends status updates
SCREENSHOT_INTERVAL=180 # How often simclient transmits screenshots
SCREENSHOT_CAPTURE_INTERVAL=180 # How often display_manager captures screenshots
DISPLAY_CHECK_INTERVAL=15 # How often display_manager checks for new events
HEARTBEAT_INTERVAL=60
SCREENSHOT_INTERVAL=180
SCREENSHOT_CAPTURE_INTERVAL=180
DISPLAY_CHECK_INTERVAL=15
# File/API Server (used to download presentation files)
# Defaults to MQTT_BROKER host with port 8000 and http scheme
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_HOST=
FILE_SERVER_PORT=8000
FILE_SERVER_SCHEME=http
# HDMI-CEC TV Control (optional)
CEC_ENABLED=true # Enable automatic TV power control
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
CEC_ENABLED=true
CEC_DEVICE=0
CEC_TURN_OFF_DELAY=30
CEC_POWER_ON_WAIT=5
CEC_POWER_OFF_WAIT=5
POWER_CONTROL_MODE=local
```
Mode summary:
- `POWER_CONTROL_MODE=local`: local event-time CEC only.
- `POWER_CONTROL_MODE=hybrid`: prefer fresh MQTT intent, fallback to local timing.
- `POWER_CONTROL_MODE=mqtt`: MQTT intent authoritative, with safe fallback behavior.
### 3. Start Services
```bash
# Start MQTT client (handles events, heartbeat, discovery)
cd ~/infoscreen-dev/src
python3 simclient.py
```
# In another terminal: Start Display Manager
In a second terminal:
```bash
cd ~/infoscreen-dev/src
python3 display_manager.py
```
Or use the startup script:
Or use the helper 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. **Server-side rendering**: PPTX files are converted to PDF by the server using Gotenberg
2. **Client receives PDFs**: Events contain pre-rendered PDF files ready for display
3. **Direct display**: PDF files are displayed directly with Impressive (no client-side conversion needed)
4. **Auto-advance** uses Impressive's built-in `--auto` parameter
5. **Loop mode** uses Impressive's `--wrap` parameter (infinite loop)
6. **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).
- Effective playback volume is calculated as `event.video.volume * client_config.audio.video_volume_multiplier` and then mapped to VLC's 0100 scale. Example: `volume: 0.8` with `audio.video_volume_multiplier: 0.5` results in 40% VLC volume.
- If `python-vlc` is not installed, the Display Manager will fall back to launching the external `vlc` binary.
- External VLC audio rendering behavior:
- When `muted: true` (or effective volume resolves to 0), fallback starts VLC with `--no-audio`.
- When not muted, fallback applies startup loudness with `--gain=<0.00-1.00>` derived from effective volume.
- Runtime volume updates are best-effort in `python-vlc` mode; external VLC fallback is startup-parameter based.
- HDMI-CEC remains the recommended mechanism for TV power control only. TV volume via CEC is not implemented because support is device-dependent and much less reliable than controlling VLC directly.
- The client-wide multiplier is intended to be sent over the existing MQTT config topic `infoscreen/{client_id}/config` and is persisted locally in `src/config/client_settings.json` for the Display Manager.
- Fullscreen behavior:
- External VLC fallback uses `--fullscreen`.
- `python-vlc` mode enforces fullscreen on startup and retries fullscreen toggling briefly because video outputs may attach asynchronously.
- 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.
- Monitoring PID behavior:
- External VLC fallback reports the external `vlc` process PID.
- `python-vlc` mode is in-process, so monitoring reports the `display_manager.py` runtime PID.
## 🌐 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 Connectivity
```bash
./scripts/test-mqtt.sh
```
Verifies MQTT broker connectivity and topic access.
### Test Screenshot Capture
```bash
./scripts/test-screenshot.sh
```
Captures test screenshot for dashboard monitoring.
**Manual test:**
```bash
export SCREENSHOT_ALWAYS=1
export SCREENSHOT_CAPTURE_INTERVAL=5
python3 src/display_manager.py &
sleep 15
ls -lh src/screenshots/
```
## 🔧 Configuration Details
### Environment Variables
All configuration is done via `.env` file in the project root. Copy `.env.template` to `.env` and adjust values for your environment.
#### Environment
- `ENV` - Environment mode: `development` or `production`
- **Important:** CEC TV control is automatically disabled in `development` mode
- `DEBUG_MODE` - Enable debug output: `1` (on) or `0` (off)
- `LOG_LEVEL` - Logging verbosity: `DEBUG`, `INFO`, `WARNING`, or `ERROR`
#### MQTT Configuration
- `MQTT_BROKER` - **Required.** MQTT broker IP address or hostname
- `MQTT_PORT` - MQTT broker port (default: `1883`)
- `MQTT_USERNAME` - Optional. MQTT authentication username (if broker requires it)
- `MQTT_PASSWORD` - Optional. MQTT authentication password (if broker requires it)
#### Timing Configuration (seconds)
- `HEARTBEAT_INTERVAL` - How often client sends status updates to server (default: `60`)
- `SCREENSHOT_INTERVAL` - How often simclient transmits screenshots via MQTT (default: `180`)
- `SCREENSHOT_CAPTURE_INTERVAL` - How often display_manager captures screenshots (default: `180`)
- `DISPLAY_CHECK_INTERVAL` - How often display_manager checks for new events (default: `15`)
#### Screenshot Configuration
- `SCREENSHOT_ALWAYS` - Force screenshot capture even when no display is active
- `0` - In production: capture only when a display process is active; in development: periodic idle captures are allowed so dashboard stays fresh
- `1` - Always capture screenshots (useful for testing)
#### File/API Server Configuration
These settings control how the client downloads presentation files and other content.
## Runtime Model
- `FILE_SERVER_HOST` - Optional. File server hostname/IP. Defaults to `MQTT_BROKER` if empty
- `FILE_SERVER_PORT` - File server port (default: `8000`)
- `FILE_SERVER_SCHEME` - Protocol: `http` or `https` (default: `http`)
- `FILE_SERVER_BASE_URL` - Optional. Full base URL override (e.g., `http://192.168.1.100:8000`)
- When set, this takes precedence over HOST/PORT/SCHEME settings
The client runs as two cooperating processes:
#### HDMI-CEC TV Control (Optional)
Automatic TV power management based on event scheduling.
- `src/simclient.py`: MQTT communication, discovery, heartbeats, event ingestion, dashboard publishing, power intent intake.
- `src/display_manager.py`: display orchestration, HDMI-CEC, screenshots, local runtime health state.
- `CEC_ENABLED` - Enable automatic TV control: `true` or `false`
- **Note:** Automatically disabled when `ENV=development` to avoid TV cycling during testing
- `CEC_DEVICE` - Target CEC device address (recommended: `0` for TV)
- `CEC_TURN_OFF_DELAY` - Seconds to wait before turning off TV after last event ends (default: `30`)
- `CEC_POWER_ON_WAIT` - Seconds to wait after power ON command for TV to boot (default: `5`)
- `CEC_POWER_OFF_WAIT` - Seconds to wait after power OFF command (default: `5`, increase for slower TVs)
Important runtime files:
### File Server URL Resolution
- `src/current_event.json`: active event from scheduler.
- `src/current_process_health.json`: process health bridge for dashboard and monitoring.
- `src/power_intent_state.json`: latest validated MQTT power intent.
- `src/power_state.json`: last applied power action telemetry.
- `src/screenshots/`: shared screenshot directory.
The MQTT client ([src/simclient.py](src/simclient.py)) downloads presentation files and videos from the configured file server.
## Content Types
**URL Rewriting:**
- Event URLs using placeholder host `server` (e.g., `http://server:8000/...`) are automatically rewritten to the configured file server
- By default, file server = `MQTT_BROKER` host with port `8000` and `http` scheme
- Use `FILE_SERVER_BASE_URL` for complete override, or set individual HOST/PORT/SCHEME variables
### Presentations
**Best practices:**
- Keep inline comments in `.env` after a space and `#` to avoid parsing issues
- Match the scheme (`http`/`https`) to your actual server configuration
- For HTTPS or non-standard ports, explicitly set `FILE_SERVER_SCHEME` and `FILE_SERVER_PORT`
Presentations are rendered server-side to PDF and displayed with Impressive. Auto-advance, loop, page progress, and auto-progress are supported.
### MQTT Topics
See [IMPRESSIVE_INTEGRATION.md](IMPRESSIVE_INTEGRATION.md) for full behavior, event examples, and troubleshooting.
#### Client → Server
- `infoscreen/discovery` - Initial client announcement
- `infoscreen/{client_id}/heartbeat` - Regular status updates
- `infoscreen/{client_id}/dashboard` - Dashboard payload v2 (grouped schema: message/content/runtime/metadata, includes screenshot base64, capture type, schema version)
- `infoscreen/{client_id}/health` - Process health state (`event_id`, process, pid, status)
- `infoscreen/{client_id}/logs/error` - Forwarded client error logs
- `infoscreen/{client_id}/logs/warn` - Forwarded client warning logs
### Videos
#### 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
Video events support:
### Client Identification
- `url`
- `autoplay`
- `loop`
- `volume`
**Hardware Token:** SHA256 hash of:
- CPU serial number
- MAC addresses (all network interfaces)
The Display Manager prefers `python-vlc`; if unavailable it falls back to the external VLC binary.
**Persistent UUID:** Stored in `src/config/client_uuid.txt`
### Web Pages
**Group Membership:** Stored in `src/config/last_group_id.txt`
Web and WebUntis events are displayed in Chromium kiosk mode.
## 🔍 Troubleshooting
## TV Power Intent
### Display Manager doesn't start presentations
Phase 1 TV power coordination uses the group topic:
**Check Impressive installation:**
```bash
which impressive
# If not found: sudo apt-get install impressive
```
- `infoscreen/groups/{group_id}/power/intent`
**Check logs:**
```bash
tail -f logs/display_manager.log
```
Key references:
**Check disk space:**
```bash
df -h
```
- Frozen contract: [TV_POWER_INTENT_SERVER_CONTRACT_V1.md](TV_POWER_INTENT_SERVER_CONTRACT_V1.md)
- Rollout and canary testing: [TV_POWER_RUNBOOK.md](TV_POWER_RUNBOOK.md)
- Client implementation handoff: [TV_POWER_HANDOFF_CLIENT.md](TV_POWER_HANDOFF_CLIENT.md)
**Note:** PPTX conversion happens server-side via Gotenberg. The client only receives and displays pre-rendered PDF files.
## Testing
### Slides don't auto-advance
Use the helper scripts in `scripts/` for focused tests:
**Verify event JSON:**
- `auto_advance: true` is set
- `slide_interval` is specified (default: 10)
- `./scripts/test-display-manager.sh`: event and process testing.
- `./scripts/test-impressive.sh`: single-play presentation.
- `./scripts/test-impressive-loop.sh`: looping presentation.
- `./scripts/test-mqtt.sh`: MQTT broker connectivity.
- `./scripts/test-screenshot.sh`: screenshot capture.
- `./scripts/test-hdmi-cec.sh`: HDMI-CEC diagnostics and runtime state inspection.
- `./scripts/test-power-intent.sh`: MQTT power intent publishing, rejection tests, and telemetry checks.
**Test Impressive directly:**
```bash
./scripts/test-impressive.sh
```
## Troubleshooting
### Presentation doesn't loop
Use the specialist docs instead of treating this file as the full troubleshooting manual:
**Verify event JSON:**
- `loop: true` is set
- Presentation and Impressive issues: [IMPRESSIVE_INTEGRATION.md](IMPRESSIVE_INTEGRATION.md)
- HDMI-CEC setup and TV control: [HDMI_CEC_SETUP.md](HDMI_CEC_SETUP.md)
- Screenshot race condition and metadata sync: [SCREENSHOT_MQTT_FIX.md](SCREENSHOT_MQTT_FIX.md)
- Monitoring and dashboard behavior: [CLIENT_MONITORING_SETUP.md](CLIENT_MONITORING_SETUP.md)
- Developer-oriented MQTT/event details: [src/README.md](src/README.md)
**Test loop mode:**
```bash
./scripts/test-impressive-loop.sh
```
Quick checks:
### File downloads fail
- Follow logs: `tail -f logs/display_manager.log src/simclient.log`
- Inspect screenshots: `ls -lh src/screenshots/`
- Inspect power state: `cat src/power_intent_state.json` and `cat src/power_state.json`
- Restart both services: `./scripts/restart-all.sh`
Symptoms:
- `Failed to resolve 'server'` or `NameResolutionError` when downloading files
- `Invalid URL 'http # http or https://...'` in `logs/simclient.log`
## Deployment
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)
For production you typically run both `simclient.py` and `display_manager.py` via systemd or Docker.
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
- Container setup: [src/CONTAINER_TRANSITION.md](src/CONTAINER_TRANSITION.md)
- Production compose file: [src/docker-compose.production.yml](src/docker-compose.production.yml)
- Display manager architecture: [src/DISPLAY_MANAGER.md](src/DISPLAY_MANAGER.md)
Expected healthy log sequence:
- `Lade Datei herunter von: http://<broker-ip>:8000/...`
- Followed by `"GET /... HTTP/1.1" 200` and `Datei erfolgreich heruntergeladen:`
If running directly on the host, ensure:
### VLC hardware decode / renderer issues
- the display session is available (`DISPLAY` / `XAUTHORITY` for X11),
- the screenshot tools for your session type are installed,
- `ENV=production` is set when you want HDMI-CEC active.
If you see messages like:
## Documentation Map
```
[h264_v4l2m2m @ ...] Could not find a valid device
[h264_v4l2m2m @ ...] can't configure decoder
[... ] avcodec decoder error: cannot start codec (h264_v4l2m2m)
```
### Operator / Deployment
that indicates libVLC / ffmpeg attempted to use the platform V4L2 M2M hardware decoder but the kernel/device isn't available. Options to resolve:
- [QUICK_REFERENCE.md](QUICK_REFERENCE.md)
- [HDMI_CEC_SETUP.md](HDMI_CEC_SETUP.md)
- [TV_POWER_RUNBOOK.md](TV_POWER_RUNBOOK.md)
- [CLIENT_MONITORING_SETUP.md](CLIENT_MONITORING_SETUP.md)
- [CHANGELOG.md](CHANGELOG.md)
- 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:
### Feature-Specific
```bash
vlc --avcodec-hw=none 'http://<your-video-url>'
```
- [IMPRESSIVE_INTEGRATION.md](IMPRESSIVE_INTEGRATION.md)
- [SCREENSHOT_MQTT_FIX.md](SCREENSHOT_MQTT_FIX.md)
- [SCHEDULER_FIELDS_SUPPORT.md](SCHEDULER_FIELDS_SUPPORT.md)
- [SERVER_VOLUME_CONTROL_SETUP.md](SERVER_VOLUME_CONTROL_SETUP.md)
Or modify `src/display_manager.py` to create the libVLC instance with software-decoding forced:
### Development / Internal
```python
instance = vlc.Instance('--avcodec-hw=none', '--no-video-title-show', '--no-video-deco')
```
- [src/README.md](src/README.md)
- [src/DISPLAY_MANAGER.md](src/DISPLAY_MANAGER.md)
- [src/IMPLEMENTATION_SUMMARY.md](src/IMPLEMENTATION_SUMMARY.md)
- [TV_POWER_COORDINATION_TASKLIST.md](TV_POWER_COORDINATION_TASKLIST.md)
- [TV_POWER_HANDOFF_SERVER.md](TV_POWER_HANDOFF_SERVER.md)
This is the fastest workaround if hardware decode is not required or not available on the device.
## Contributing
### MQTT connection issues
Before changing runtime behavior:
**Test broker connectivity:**
```bash
./scripts/test-mqtt.sh
```
- test with the relevant helper scripts,
- verify logs stay clean,
- update the specialist doc for the feature you changed.
### MQTT reconnect and heartbeat behavior
When editing AI assistant guidance files:
- On reconnect, the client re-subscribes all topics in `on_connect` and re-sends discovery to re-register.
- Heartbeats are sent only when connected. During brief reconnect windows, Paho may return rc=4 (`NO_CONN`).
- A single rc=4 warning after broker restarts or short network stalls is expected; the next heartbeat usually succeeds.
- Investigate only if rc=4 repeats across multiple intervals without subsequent successful heartbeat logs.
- keep `.github/copilot-instructions.md` policy-focused,
- follow its "Instruction File Design Rules" section,
- avoid turning it into a shadow README.
### Monitoring and UTC timestamps
Recent project history is tracked in [CHANGELOG.md](CHANGELOG.md).
Client-side monitoring is implemented with a health-state bridge between `display_manager.py` and `simclient.py`.
- Health bridge file: `src/current_process_health.json`
- Local monitoring log: `logs/monitoring.log`
- Process states: `running`, `crashed`, `stopped`
- Restart tracking: bounded restart attempts per active event
UTC timestamp policy:
- `display_manager.log`, `simclient.log`, and `monitoring.log` are written in UTC (`...Z`)
- MQTT payload timestamps (heartbeat/dashboard/health/log messages) are UTC ISO timestamps
- Screenshot metadata timestamps are UTC ISO timestamps
This prevents daylight-saving and timezone drift issues across clients.
### VLC/PulseAudio warnings in remote sessions
Warnings such as `pulse audio output error: overflow, flushing` can appear when testing through remote desktop/audio forwarding (for example, NoMachine) or virtual/dummy display setups.
- If playback and audio are stable on a real HDMI display, this is usually non-fatal.
- If warnings appear only in remote sessions, treat them as environment-related rather than a core video playback bug.
### Screenshots not uploading
**Check which session type you're running:**
```bash
echo $WAYLAND_DISPLAY # Set if Wayland
echo $DISPLAY # Set if X11
echo $XAUTHORITY # Should point to ~/.Xauthority for X11 captures
```
If `DISPLAY` is empty for non-interactive starts (systemd/nohup/ssh), the display manager now falls back to `:0` and tries `~/.Xauthority` automatically.
**Install appropriate screenshot tool:**
```bash
# For X11:
sudo apt-get install scrot imagemagick
# For Wayland:
sudo apt-get install grim gnome-screenshot
```
**Test screenshot capture:**
```bash
export SCREENSHOT_ALWAYS=1 # Force capture even without active event
./scripts/test-screenshot.sh
ls -lh src/screenshots/
```
**Check logs for session detection:**
```bash
tail -f logs/display_manager.log | grep -i screenshot
# Should show: "Screenshot session=wayland" or "Screenshot session=x11"
```
**If you see stale dashboard images after restarts:**
```bash
cat src/screenshots/meta.json
stat src/screenshots/latest.jpg
```
- If `send_immediately` is stuck `true` for old metadata, restart both processes so simclient consumes and clears it.
- If `latest.jpg` timestamp does not move while new `screenshot_*.jpg` files appear, update to latest code (fix for periodic `latest.jpg` update path) and restart display_manager.
**Verify simclient is reading screenshots:**
```bash
tail -f logs/simclient.log | grep -i screenshot
# Should show: "Dashboard published: schema=2.0 type=periodic screenshot=latest.jpg"
# For event transitions: "Dashboard published: schema=2.0 type=event_start ..."
```
## 📚 Documentation
- **IMPRESSIVE_INTEGRATION.md** - Detailed presentation system documentation
- **HDMI_CEC_SETUP.md** - HDMI-CEC setup and troubleshooting
- **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
```
## 📺 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
```bash
# 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
```bash
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
```
## 🤝 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
## 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:** March 2026
**Status:** ✅ Production Ready
**Tested On:** Raspberry Pi 5, Raspberry Pi OS (Bookworm)
## Recent Changes
### November 2025
- Screenshot pipeline implemented with a two-process model (`display_manager.py` capture, `simclient.py` transmission).
- Wayland/X11 screenshot tool fallback chains added.
- Dashboard payload format extended with screenshot and system metadata.
- Scheduler event type support extended (`presentation`, `webuntis`, `webpage`, `website`).
- Website autoscroll support added (CDP injection + extension fallback).
### March 2026
- Event-trigger screenshots (`event_start`, `event_stop`) hardened against periodic overwrite races.
- `latest.jpg` and `meta.json` synchronization improved for reliable dashboard updates.
- Stale/invalid pending trigger metadata now self-heals instead of blocking periodic updates.
- Display environment fallbacks (`DISPLAY=:0`, `XAUTHORITY`) improved for non-interactive starts.
- Development mode allows periodic idle captures to keep dashboard previews fresh when no event is active.
- Event-triggered screenshots added: `display_manager` captures a screenshot shortly after every event start and stop and signals `simclient` via `meta.json` (`send_immediately=true`). Capture delays are content-type aware (presentation: 4s, video: 2s, web: 5s, configurable via `.env`).
- `simclient` screenshot service thread now runs on a 1-second tick instead of a blocking sleep, so triggered sends fire within ≤1s of the `meta.json` signal.
- Dashboard payload migrated to grouped v2 schema (`message`, `content`, `runtime`, `metadata`). Legacy flat fields removed. `metadata.schema_version` is `"2.0"`. Payload assembly centralized in `_build_dashboard_payload()`.
- Tunable trigger delays added: `SCREENSHOT_TRIGGER_DELAY_PRESENTATION`, `SCREENSHOT_TRIGGER_DELAY_VIDEO`, `SCREENSHOT_TRIGGER_DELAY_WEB`.
- Rapid event switches handled safely: pending trigger timer is cancelled and replaced when a new event starts before the delay expires.