mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-04-10 13:02:59 +00:00
* docs: rename FONT_MANAGER_USAGE.md to FONT_MANAGER.md Renamed for clearer naming convention. Part of documentation consolidation effort. * docs: consolidate Plugin Store guides (2→1) Merged: - PLUGIN_STORE_USER_GUIDE.md - PLUGIN_STORE_QUICK_REFERENCE.md Into: PLUGIN_STORE_GUIDE.md - Unified writing style to professional technical - Added Quick Reference section at top for easy access - Removed duplicate content - Added cross-references to related documentation - Updated formatting to match style guidelines * docs: create user-focused Web Interface Guide Created WEB_INTERFACE_GUIDE.md consolidating: - V3_INTERFACE_README.md (technical details) - User-facing interface documentation - Focused on end-user tasks and navigation - Removed technical implementation details - Added common tasks section - Included troubleshooting - Professional technical writing style * docs: consolidate WiFi setup guides (4→1) Merged: - WIFI_SETUP.md - OPTIMAL_WIFI_AP_FAILOVER_SETUP.md - AP_MODE_MANUAL_ENABLE.md - WIFI_ETHERNET_AP_MODE_FIX.md (behavior documentation) Into: WIFI_NETWORK_SETUP.md - Comprehensive coverage of WiFi setup and configuration - Clear explanation of AP mode failover and grace period - Configuration scenarios and best practices - Troubleshooting section combining all sources - Professional technical writing style - Added quick reference table for behavior * docs: consolidate troubleshooting guides (4→1) Merged: - TROUBLESHOOTING_QUICK_START.md - WEB_INTERFACE_TROUBLESHOOTING.md - CAPTIVE_PORTAL_TROUBLESHOOTING.md - WEATHER_TROUBLESHOOTING.md Into: TROUBLESHOOTING.md - Organized by issue category (web, WiFi, plugins) - Comprehensive diagnostic commands reference - Quick diagnosis steps at the top - Service file template preserved - Complete diagnostic script included - Professional technical writing style * docs: create consolidated Advanced Features guide Merged: - VEGAS_SCROLL_MODE.md - ON_DEMAND_DISPLAY_QUICK_START.md - ON_DEMAND_DISPLAY_API.md - ON_DEMAND_CACHE_MANAGEMENT.md - BACKGROUND_SERVICE_README.md - PERMISSION_MANAGEMENT_GUIDE.md Into: ADVANCED_FEATURES.md - Comprehensive guide covering all advanced features - Vegas scroll mode with integration examples - On-demand display with API reference - Cache management troubleshooting - Background service documentation - Permission management patterns - Professional technical writing style * docs: create Getting Started guide for first-time users Created GETTING_STARTED.md: - Quick start guide (5 minutes) - Initial configuration walkthrough - Common first-time issues and solutions - Next steps and quick reference - User-friendly tone for beginners - Links to detailed documentation * docs: archive consolidated source files and ephemeral docs Archived files that have been consolidated: - Plugin Store guides (2 files → PLUGIN_STORE_GUIDE.md) - Web Interface guide (V3_INTERFACE_README.md → WEB_INTERFACE_GUIDE.md) - WiFi Setup guides (4 files → WIFI_NETWORK_SETUP.md) - Troubleshooting guides (4 files → TROUBLESHOOTING.md) - Advanced Features (6 files → ADVANCED_FEATURES.md) Archived ephemeral/debug documentation: - DEBUG_WEB_ISSUE.md - BROWSER_ERRORS_EXPLANATION.md - FORM_VALIDATION_FIXES.md - WEB_UI_RELIABILITY_IMPROVEMENTS.md - CAPTIVE_PORTAL_TESTING.md - NEXT_STEPS_COMMANDS.md - STATIC_IMAGE_MULTI_UPLOAD_PLAN.md - RECONNECT_AFTER_CAPTIVE_PORTAL_TESTING.md Archived implementation summaries: - PLUGIN_CONFIG_TABS_SUMMARY.md - PLUGIN_CONFIG_SYSTEM_VERIFICATION.md - PLUGIN_SCHEMA_AUDIT_SUMMARY.md - STARTUP_OPTIMIZATION_SUMMARY.md - PLUGIN_DISPATCH_IMPLEMENTATION.md - NESTED_SCHEMA_IMPLEMENTATION.md - AP_MODE_MANUAL_ENABLE_CHANGES.md - PLUGIN_CONFIG_SYSTEM_EXPLANATION.md Total archived: 27 files Preserves git history while cleaning up main docs directory * docs: rename API_REFERENCE.md to REST_API_REFERENCE.md Renamed for clarity - this is specifically the REST API reference for the web interface, not a general API reference. * docs: update README.md to reflect consolidated documentation structure Updated documentation index: - Reflects new consolidated guides (51 → 16-17 files) - Updated Quick Start sections with new file names - Added consolidation history (January 2026) - Updated file references (API_REFERENCE → REST_API_REFERENCE) - Documented archival of 33 files - Added benefits of consolidation - Updated statistics and highlights - Removed outdated references - Professional writing style maintained throughout * docs: add Vegas scroll mode system architecture documentation Added comprehensive internal architecture section for Vegas mode: - Component overview with diagram - VegasModeCoordinator responsibilities and main loop - StreamManager buffering strategy and content flow - PluginAdapter integration and fallback behavior - RenderPipeline 125 FPS rendering process - Component interaction flows - Thread safety patterns - Performance characteristics Covers: - How the four components work together - Initialization and render loop flows - Config update handling - Frame rate management and optimization - Memory usage and CPU characteristics --------- Co-authored-by: Chuck <chuck@example.com>
555 lines
14 KiB
Markdown
555 lines
14 KiB
Markdown
# On-Demand Display API
|
|
|
|
## Overview
|
|
|
|
The On-Demand Display API allows **manual control** of what's shown on the LED matrix. Unlike the automatic rotation or live priority system, on-demand display is **user-triggered** - typically from the web interface with a "Show Now" button.
|
|
|
|
## Use Cases
|
|
|
|
- 📺 **"Show Weather Now"** button in web UI
|
|
- 🏒 **"Show Live Game"** button for specific sports
|
|
- 📰 **"Show Breaking News"** button
|
|
- 🎵 **"Show Currently Playing"** button for music
|
|
- 🎮 **Quick preview** of any plugin without waiting for rotation
|
|
|
|
## Priority Hierarchy
|
|
|
|
The display controller processes requests in this order:
|
|
|
|
```
|
|
1. On-Demand Display (HIGHEST) ← User explicitly requested
|
|
2. Live Priority (plugins with live content)
|
|
3. Normal Rotation (automatic cycling)
|
|
```
|
|
|
|
On-demand overrides everything, including live priority.
|
|
|
|
## API Reference
|
|
|
|
### DisplayController Methods
|
|
|
|
#### `show_on_demand(mode, duration=None, pinned=False) -> bool`
|
|
|
|
Display a specific mode immediately, interrupting normal rotation.
|
|
|
|
**Parameters:**
|
|
- `mode` (str): The display mode to show (e.g., 'weather', 'hockey_live')
|
|
- `duration` (float, optional): How long to show in seconds
|
|
- `None`: Use mode's default `display_duration` from config
|
|
- `0`: Show indefinitely (until cleared)
|
|
- `> 0`: Show for exactly this many seconds
|
|
- `pinned` (bool): If True, stays on this mode until manually cleared
|
|
|
|
**Returns:**
|
|
- `True`: Mode was found and activated
|
|
- `False`: Mode doesn't exist
|
|
|
|
**Example:**
|
|
```python
|
|
# Show weather for 30 seconds then return to rotation
|
|
controller.show_on_demand('weather', duration=30)
|
|
|
|
# Show weather indefinitely
|
|
controller.show_on_demand('weather', duration=0)
|
|
|
|
# Pin to hockey live (stays until unpinned)
|
|
controller.show_on_demand('hockey_live', pinned=True)
|
|
|
|
# Use plugin's default duration
|
|
controller.show_on_demand('weather') # Uses display_duration from config
|
|
```
|
|
|
|
#### `clear_on_demand() -> None`
|
|
|
|
Clear on-demand display and return to normal rotation.
|
|
|
|
**Example:**
|
|
```python
|
|
controller.clear_on_demand()
|
|
```
|
|
|
|
#### `is_on_demand_active() -> bool`
|
|
|
|
Check if on-demand display is currently active.
|
|
|
|
**Returns:**
|
|
- `True`: On-demand mode is active
|
|
- `False`: Normal rotation or live priority
|
|
|
|
**Example:**
|
|
```python
|
|
if controller.is_on_demand_active():
|
|
print("User is viewing on-demand content")
|
|
```
|
|
|
|
#### `get_on_demand_info() -> dict`
|
|
|
|
Get detailed information about current on-demand display.
|
|
|
|
**Returns:**
|
|
```python
|
|
{
|
|
'active': True, # Whether on-demand is active
|
|
'mode': 'weather', # Current mode being displayed
|
|
'duration': 30.0, # Total duration (None if indefinite)
|
|
'elapsed': 12.5, # Seconds elapsed
|
|
'remaining': 17.5, # Seconds remaining (None if indefinite)
|
|
'pinned': False # Whether pinned
|
|
}
|
|
|
|
# Or if not active:
|
|
{
|
|
'active': False
|
|
}
|
|
```
|
|
|
|
**Example:**
|
|
```python
|
|
info = controller.get_on_demand_info()
|
|
if info['active']:
|
|
print(f"Showing {info['mode']}, {info['remaining']}s remaining")
|
|
```
|
|
|
|
## Web Interface Integration
|
|
|
|
### API Endpoint Example
|
|
|
|
```python
|
|
# In web_interface/blueprints/api_v3.py
|
|
|
|
from flask import jsonify, request
|
|
|
|
@api_v3.route('/display/show', methods=['POST'])
|
|
def show_on_demand():
|
|
"""Show a specific plugin on-demand"""
|
|
data = request.json
|
|
mode = data.get('mode')
|
|
duration = data.get('duration') # Optional
|
|
pinned = data.get('pinned', False) # Optional
|
|
|
|
# Get display controller instance
|
|
controller = get_display_controller()
|
|
|
|
success = controller.show_on_demand(mode, duration, pinned)
|
|
|
|
if success:
|
|
return jsonify({
|
|
'success': True,
|
|
'message': f'Showing {mode}',
|
|
'info': controller.get_on_demand_info()
|
|
})
|
|
else:
|
|
return jsonify({
|
|
'success': False,
|
|
'error': f'Mode {mode} not found'
|
|
}), 404
|
|
|
|
@api_v3.route('/display/clear', methods=['POST'])
|
|
def clear_on_demand():
|
|
"""Clear on-demand display"""
|
|
controller = get_display_controller()
|
|
controller.clear_on_demand()
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'message': 'On-demand display cleared'
|
|
})
|
|
|
|
@api_v3.route('/display/on-demand-info', methods=['GET'])
|
|
def get_on_demand_info():
|
|
"""Get on-demand display status"""
|
|
controller = get_display_controller()
|
|
info = controller.get_on_demand_info()
|
|
|
|
return jsonify(info)
|
|
```
|
|
|
|
### Frontend Example (JavaScript)
|
|
|
|
```javascript
|
|
// Show weather for 30 seconds
|
|
async function showWeather() {
|
|
const response = await fetch('/api/v3/display/show', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
mode: 'weather',
|
|
duration: 30
|
|
})
|
|
});
|
|
|
|
const data = await response.json();
|
|
if (data.success) {
|
|
updateStatus(`Showing weather for ${data.info.duration}s`);
|
|
}
|
|
}
|
|
|
|
// Pin to live hockey game
|
|
async function pinHockeyLive() {
|
|
const response = await fetch('/api/v3/display/show', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
mode: 'hockey_live',
|
|
pinned: true
|
|
})
|
|
});
|
|
|
|
const data = await response.json();
|
|
if (data.success) {
|
|
updateStatus('Pinned to hockey live');
|
|
}
|
|
}
|
|
|
|
// Clear on-demand
|
|
async function clearOnDemand() {
|
|
const response = await fetch('/api/v3/display/clear', {
|
|
method: 'POST'
|
|
});
|
|
|
|
const data = await response.json();
|
|
if (data.success) {
|
|
updateStatus('Returned to normal rotation');
|
|
}
|
|
}
|
|
|
|
// Check status
|
|
async function checkOnDemandStatus() {
|
|
const response = await fetch('/api/v3/display/on-demand-info');
|
|
const info = await response.json();
|
|
|
|
if (info.active) {
|
|
updateStatus(`On-demand: ${info.mode} (${info.remaining}s remaining)`);
|
|
} else {
|
|
updateStatus('Normal rotation');
|
|
}
|
|
}
|
|
```
|
|
|
|
### UI Example (HTML)
|
|
|
|
```html
|
|
<!-- Plugin controls -->
|
|
<div class="plugin-card">
|
|
<h3>Weather</h3>
|
|
<button onclick="showWeather()">Show Now (30s)</button>
|
|
<button onclick="showWeatherIndefinite()">Show Until Cleared</button>
|
|
<button onclick="pinWeather()">Pin Weather</button>
|
|
</div>
|
|
|
|
<!-- On-demand status display -->
|
|
<div id="on-demand-status" class="status-bar">
|
|
<span id="status-text">Normal rotation</span>
|
|
<button id="clear-btn" onclick="clearOnDemand()" style="display: none;">
|
|
Clear On-Demand
|
|
</button>
|
|
</div>
|
|
|
|
<script>
|
|
// Poll for status updates
|
|
setInterval(async () => {
|
|
const info = await fetch('/api/v3/display/on-demand-info').then(r => r.json());
|
|
|
|
const statusText = document.getElementById('status-text');
|
|
const clearBtn = document.getElementById('clear-btn');
|
|
|
|
if (info.active) {
|
|
let text = `On-demand: ${info.mode}`;
|
|
if (info.remaining) {
|
|
text += ` (${Math.ceil(info.remaining)}s)`;
|
|
} else if (info.pinned) {
|
|
text += ' (pinned)';
|
|
}
|
|
statusText.textContent = text;
|
|
clearBtn.style.display = 'inline-block';
|
|
} else {
|
|
statusText.textContent = 'Normal rotation';
|
|
clearBtn.style.display = 'none';
|
|
}
|
|
}, 1000); // Update every second
|
|
</script>
|
|
```
|
|
|
|
## Behavior Details
|
|
|
|
### Duration Modes
|
|
|
|
| Duration Value | Behavior | Use Case |
|
|
|---------------|----------|----------|
|
|
| `None` | Use plugin's `display_duration` from config | Default behavior |
|
|
| `0` | Show indefinitely until cleared | Quick preview |
|
|
| `> 0` | Show for exactly N seconds | Timed preview |
|
|
| `pinned=True` | Stay on mode until unpinned | Extended viewing |
|
|
|
|
### Auto-Clear Behavior
|
|
|
|
On-demand display automatically clears when:
|
|
- Duration expires (if set and > 0)
|
|
- User manually clears it
|
|
- System restarts
|
|
|
|
On-demand does NOT clear when:
|
|
- `duration=0` (indefinite)
|
|
- `pinned=True`
|
|
- Live priority content appears (on-demand still has priority)
|
|
|
|
### Interaction with Live Priority
|
|
|
|
```python
|
|
# Scenario 1: On-demand overrides live priority
|
|
controller.show_on_demand('weather', duration=30)
|
|
# → Shows weather even if live game is happening
|
|
|
|
# Scenario 2: After on-demand expires, live priority takes over
|
|
controller.show_on_demand('weather', duration=10)
|
|
# → Shows weather for 10s
|
|
# → If live game exists, switches to live game
|
|
# → Otherwise returns to normal rotation
|
|
```
|
|
|
|
## Use Case Examples
|
|
|
|
### Example 1: Quick Weather Check
|
|
|
|
```python
|
|
# User clicks "Show Weather" button
|
|
controller.show_on_demand('weather', duration=30)
|
|
# Shows weather for 30 seconds, then returns to rotation
|
|
```
|
|
|
|
### Example 2: Monitor Live Game
|
|
|
|
```python
|
|
# User clicks "Watch Live Game" button
|
|
controller.show_on_demand('hockey_live', pinned=True)
|
|
# Stays on live game until user clicks "Back to Rotation"
|
|
```
|
|
|
|
### Example 3: Preview Plugin
|
|
|
|
```python
|
|
# User clicks "Preview" in plugin settings
|
|
controller.show_on_demand('my-plugin', duration=15)
|
|
# Shows plugin for 15 seconds to test configuration
|
|
```
|
|
|
|
### Example 4: Emergency Override
|
|
|
|
```python
|
|
# Admin needs to show important message
|
|
controller.show_on_demand('text-display', pinned=True)
|
|
# Display stays on message until admin clears it
|
|
```
|
|
|
|
## Testing
|
|
|
|
### Manual Test from Python
|
|
|
|
```python
|
|
# Access display controller
|
|
from src.display_controller import DisplayController
|
|
controller = DisplayController() # Or get existing instance
|
|
|
|
# Test show on-demand
|
|
controller.show_on_demand('weather', duration=20)
|
|
print(controller.get_on_demand_info())
|
|
|
|
# Test clear
|
|
time.sleep(5)
|
|
controller.clear_on_demand()
|
|
print(controller.get_on_demand_info())
|
|
```
|
|
|
|
### Test with Web API
|
|
|
|
```bash
|
|
# Show weather for 30 seconds
|
|
curl -X POST http://pi-ip:5001/api/v3/display/show \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"mode": "weather", "duration": 30}'
|
|
|
|
# Check status
|
|
curl http://pi-ip:5001/api/v3/display/on-demand-info
|
|
|
|
# Clear on-demand
|
|
curl -X POST http://pi-ip:5001/api/v3/display/clear
|
|
```
|
|
|
|
### Monitor Logs
|
|
|
|
```bash
|
|
sudo journalctl -u ledmatrix -f | grep -i "on-demand"
|
|
```
|
|
|
|
Expected output:
|
|
```
|
|
On-demand display activated: weather (duration: 30s, pinned: False)
|
|
On-demand display expired after 30.1s
|
|
Clearing on-demand display: weather
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
### 1. Provide Visual Feedback
|
|
|
|
Always show users when on-demand is active:
|
|
|
|
```javascript
|
|
// Update UI to show on-demand status
|
|
function updateOnDemandUI(info) {
|
|
const banner = document.getElementById('on-demand-banner');
|
|
if (info.active) {
|
|
banner.style.display = 'block';
|
|
banner.textContent = `Showing: ${info.mode}`;
|
|
if (info.remaining) {
|
|
banner.textContent += ` (${Math.ceil(info.remaining)}s)`;
|
|
}
|
|
} else {
|
|
banner.style.display = 'none';
|
|
}
|
|
}
|
|
```
|
|
|
|
### 2. Default to Timed Display
|
|
|
|
Unless explicitly requested, use a duration:
|
|
|
|
```python
|
|
# Good: Auto-clears after 30 seconds
|
|
controller.show_on_demand('weather', duration=30)
|
|
|
|
# Risky: Stays indefinitely
|
|
controller.show_on_demand('weather', duration=0)
|
|
```
|
|
|
|
### 3. Validate Modes
|
|
|
|
Check if mode exists before showing:
|
|
|
|
```python
|
|
# Get available modes
|
|
available_modes = controller.available_modes + list(controller.plugin_modes.keys())
|
|
|
|
if mode in available_modes:
|
|
controller.show_on_demand(mode, duration=30)
|
|
else:
|
|
return jsonify({'error': 'Mode not found'}), 404
|
|
```
|
|
|
|
### 4. Handle Concurrent Requests
|
|
|
|
Last request wins:
|
|
|
|
```python
|
|
# Request 1: Show weather
|
|
controller.show_on_demand('weather', duration=30)
|
|
|
|
# Request 2: Show hockey (overrides weather)
|
|
controller.show_on_demand('hockey_live', duration=20)
|
|
# Hockey now shows for 20s, weather request is forgotten
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### On-Demand Not Working
|
|
|
|
**Check 1:** Verify mode exists
|
|
```python
|
|
info = controller.get_on_demand_info()
|
|
print(f"Active: {info['active']}, Mode: {info.get('mode')}")
|
|
print(f"Available modes: {controller.available_modes}")
|
|
```
|
|
|
|
**Check 2:** Check logs
|
|
```bash
|
|
sudo journalctl -u ledmatrix -f | grep "on-demand\|available modes"
|
|
```
|
|
|
|
### On-Demand Not Clearing
|
|
|
|
**Check if pinned:**
|
|
```python
|
|
info = controller.get_on_demand_info()
|
|
if info['pinned']:
|
|
print("Mode is pinned - must clear manually")
|
|
controller.clear_on_demand()
|
|
```
|
|
|
|
**Check duration:**
|
|
```python
|
|
if info['duration'] == 0:
|
|
print("Duration is indefinite - must clear manually")
|
|
```
|
|
|
|
### Mode Shows But Looks Wrong
|
|
|
|
This is a **display** issue, not an on-demand issue. Check:
|
|
- Plugin's `update()` method is fetching data
|
|
- Plugin's `display()` method is rendering correctly
|
|
- Cache is not stale
|
|
|
|
## Security Considerations
|
|
|
|
### 1. Authentication Required
|
|
|
|
Always require authentication for on-demand control:
|
|
|
|
```python
|
|
@api_v3.route('/display/show', methods=['POST'])
|
|
@login_required # Add authentication
|
|
def show_on_demand():
|
|
# ... implementation
|
|
```
|
|
|
|
### 2. Rate Limiting
|
|
|
|
Prevent spam:
|
|
|
|
```python
|
|
from flask_limiter import Limiter
|
|
|
|
limiter = Limiter(app, key_func=get_remote_address)
|
|
|
|
@api_v3.route('/display/show', methods=['POST'])
|
|
@limiter.limit("10 per minute") # Max 10 requests per minute
|
|
def show_on_demand():
|
|
# ... implementation
|
|
```
|
|
|
|
### 3. Input Validation
|
|
|
|
Sanitize mode names:
|
|
|
|
```python
|
|
import re
|
|
|
|
def validate_mode(mode):
|
|
# Only allow alphanumeric, underscore, hyphen
|
|
if not re.match(r'^[a-zA-Z0-9_-]+$', mode):
|
|
raise ValueError("Invalid mode name")
|
|
return mode
|
|
```
|
|
|
|
## Implementation Checklist
|
|
|
|
- [ ] Add API endpoint to web interface
|
|
- [ ] Add "Show Now" buttons to plugin UI
|
|
- [ ] Add on-demand status indicator
|
|
- [ ] Add "Clear" button when on-demand active
|
|
- [ ] Add authentication/authorization
|
|
- [ ] Add rate limiting
|
|
- [ ] Test with multiple plugins
|
|
- [ ] Test duration expiration
|
|
- [ ] Test pinned mode
|
|
- [ ] Document for end users
|
|
|
|
## Future Enhancements
|
|
|
|
Consider adding:
|
|
1. **Queue system** - Queue multiple on-demand requests
|
|
2. **Scheduled on-demand** - Show mode at specific time
|
|
3. **Recurring on-demand** - Show every N minutes
|
|
4. **Permission levels** - Different users can show different modes
|
|
5. **History tracking** - Log who triggered what and when
|
|
|