docs: correct semantically stale content across the user and developer guides

A second-pass content audit checked the guides' substantive claims
against the code (the first pass only fixed mechanical drift). Fixes:

- GETTING_STARTED: described booting a prebuilt SD image and seeing
  default clock/weather plugins — neither exists. Now documents the real
  install (Pi OS Lite + one-shot installer / first_time_install.sh) and
  that displays come from the Plugin Store. Duration and ordering
  instructions moved to the Rotation tab where the controls actually
  live.
- WEB_INTERFACE_GUIDE: three whole tabs were undocumented (Rotation,
  Backup & Restore, Tools) and the Display tab's Vegas Scroll section
  was unmentioned. Fonts overrides are per display element (not per
  plugin); Logs has an Auto-scroll checkbox (not a Pause button); the
  aspirational keyboard-shortcut list and no-JS claim removed.
- TROUBLESHOOTING: the hand-written service-file template (wrong user,
  wrong ExecStart, dropped the autostart gate) replaced with the real
  systemd/ units + install scripts; recovery steps no longer copy
  placeholder units verbatim; WiFi curl endpoint corrected to /api/v3/;
  cache-clearing advice now targets the real cache locations.
- ADVANCED_FEATURES: removed a false claim that CacheManager has no
  delete(); fixed two example snippets that raise TypeError
  (BackgroundDataService and get_config_file_mode signatures); fixed
  cache paths, a 5-minute TTL that is actually 1 hour, and the vegas
  table now links the complete 26-key reference.
- EMULATOR_SETUP_GUIDE: documented run.py flags that don't exist
  (--plugin/--test-plugins) removed in favor of dev_server.py and
  check_plugin.py; shipped emulator config values corrected (browser
  adapter default on :8888, not pygame).
- PLUGIN_QUICK_REFERENCE: drag-and-drop reordering is shipped, not
  'not yet supported'; discovery-fallback and registry-repo claims
  corrected. PLUGIN_API_REFERENCE: get_vegas_segment_width returns
  panels, not pixels. CONTRIBUTING: the repo uses flake8/mypy/bandit
  pre-commit hooks, not black/ruff, and tests need requirements-test.txt.
- SKIN_SYSTEM/DEVELOPER_QUICK_REFERENCE: stale module paths.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SXb4mKcAkVaxkeTb3YnAdr
This commit is contained in:
Claude
2026-08-06 01:56:13 +00:00
parent eca41f8daa
commit 4808132436
10 changed files with 255 additions and 196 deletions
+7 -4
View File
@@ -40,7 +40,7 @@ improvements, and code changes.
## Running the tests ## Running the tests
```bash ```bash
pip install -r requirements.txt pip install -r requirements.txt -r requirements-test.txt
pytest pytest
``` ```
@@ -57,9 +57,12 @@ integration tests.
`docs/<short-description>`. `docs/<short-description>`.
3. **Keep PRs focused.** One conceptual change per PR. If you find 3. **Keep PRs focused.** One conceptual change per PR. If you find
adjacent bugs while working, fix them in a separate PR. adjacent bugs while working, fix them in a separate PR.
4. **Follow the existing code style.** Python code uses standard 4. **Follow the existing code style.** The pre-commit hooks run
`black`/`ruff` conventions; HTML/JS in `web_interface/` follows the `flake8` (E9, F63, F7, F82 plus bugbear `B` checks), `mypy` on
patterns already in `templates/v3/` and `static/v3/`. `src/`, `bandit`, and `gitleaks` — install them with
`pre-commit install` so they run on every commit; HTML/JS in
`web_interface/` follows the patterns already in `templates/v3/`
and `static/v3/`.
5. **Update documentation** alongside code changes. If you add a 5. **Update documentation** alongside code changes. If you add a
config key, document it in the relevant `*.md` file (or, for config key, document it in the relevant `*.md` file (or, for
plugins, in `config_schema.json` so the form is auto-generated). plugins, in `config_schema.json` so the form is auto-generated).
+41 -23
View File
@@ -47,6 +47,11 @@ Enable Vegas mode in `config/config.json`:
} }
``` ```
Vegas mode can also be configured entirely from the web UI — the
**Display** tab has a Vegas Scroll Mode section (enable toggle, scroll
speed, separator width, dynamic duration, and more), so hand-editing
JSON is optional.
**Configuration Options:** **Configuration Options:**
| Setting | Default | Description | | Setting | Default | Description |
@@ -57,7 +62,11 @@ Enable Vegas mode in `config/config.json`:
| `plugin_order` | `[]` | Plugin display order (empty = auto) | | `plugin_order` | `[]` | Plugin display order (empty = auto) |
| `excluded_plugins` | `[]` | Plugins to exclude from Vegas mode | | `excluded_plugins` | `[]` | Plugins to exclude from Vegas mode |
| `target_fps` | `125` | Target frame rate | | `target_fps` | `125` | Target frame rate |
| `buffer_ahead` | `2` | Number of panels to render ahead | | `buffer_ahead` | `2` | Number of plugins buffered ahead |
This table is a subset — `display.vegas_scroll` supports 26 keys in
total. See the full list in
[CONFIG_REFERENCE.md](CONFIG_REFERENCE.md#displayvegas_scroll--continuous-scroll-mode).
### Per-Plugin Configuration ### Per-Plugin Configuration
@@ -79,9 +88,13 @@ Override Vegas behavior for specific plugins:
| Setting | Values | Description | | Setting | Values | Description |
|---------|--------|-------------| |---------|--------|-------------|
| `vegas_mode` | `scroll`, `fixed`, `static` | Display mode for this plugin | | `vegas_mode` | `scroll`, `fixed`, `static` | Display mode for this plugin |
| `vegas_panel_count` | `1-10` | Width in panels (1 panel = display width) | | `vegas_panel_count` | any positive integer | Width in panels (1 panel = display width) |
| `display_duration` | seconds | Pause duration for STATIC mode | | `display_duration` | seconds | Pause duration for STATIC mode |
Plugins may also set `vegas_overflow` and `vegas_max_width_screens` in
their config section to control how oversized content is handled (see
`PluginManager` in `src/plugin_system/plugin_manager.py`).
### Plugin Integration (Developer Guide) ### Plugin Integration (Developer Guide)
**1. Implement Content Method:** **1. Implement Content Method:**
@@ -451,7 +464,7 @@ time when something is active.
### REST API Reference ### REST API Reference
The API is mounted at `/api/v3` (`web_interface/app.py:144`). The API is mounted at `/api/v3` (`web_interface/app.py:199`).
#### Start On-Demand Display #### Start On-Demand Display
@@ -518,13 +531,15 @@ curl http://localhost:5000/api/v3/display/on-demand/status
> There is no public Python on-demand API. The display controller's > There is no public Python on-demand API. The display controller's
> on-demand machinery is internal — drive it through the REST endpoints > on-demand machinery is internal — drive it through the REST endpoints
> above (or the web UI buttons), which write a request into the cache > above (or the web UI buttons). The API handlers
> manager under the `display_on_demand_request` key > (`start_on_demand_display()` / `stop_on_demand_display()` in
> (`web_interface/blueprints/api_v3.py:1622,1687`) that the controller > `web_interface/blueprints/api_v3.py`) write a request into the cache
> polls at `src/display_controller.py:921`. A separate > manager under the `display_on_demand_request` key, which
> `DisplayController._poll_on_demand_requests()`
> (`src/display_controller.py`) picks up. A separate
> `display_on_demand_config` key is used by the controller itself > `display_on_demand_config` key is used by the controller itself
> during activation to track what's currently running (written at > during activation (`_activate_on_demand()`) to track what's
> `display_controller.py:1195`, cleared at `:1221`). > currently running, and is cleared by `_clear_on_demand()`.
### Duration Modes ### Duration Modes
@@ -646,13 +661,13 @@ keys helps troubleshoot stuck states.
**When Set:** Every display loop iteration **When Set:** Every display loop iteration
**Auto-Cleared:** Never (continuously updated) **Auto-Cleared:** Never (continuously updated)
**4. display_on_demand_processed_id** (TTL: 5 minutes) **4. display_on_demand_processed_id** (TTL: 1 hour)
``` ```
"uuid-string-of-last-processed-request" "uuid-string-of-last-processed-request"
``` ```
**Purpose:** Prevents duplicate request processing **Purpose:** Prevents duplicate request processing
**When Set:** After processing request **When Set:** After processing request
**Auto-Cleared:** After 5 minutes TTL **Auto-Cleared:** After 1 hour TTL
### When Manual Clearing is Needed ### When Manual Clearing is Needed
@@ -685,9 +700,9 @@ keys helps troubleshoot stuck states.
The cache is stored as JSON files under one of: The cache is stored as JSON files under one of:
- `/var/cache/ledmatrix/` (preferred when the service has permission) - `/var/cache/ledmatrix/` (preferred when the service has permission)
- `~/.cache/ledmatrix/` - `~/.ledmatrix_cache/`
- `/opt/ledmatrix/cache/` - `/opt/ledmatrix/cache/`
- `/tmp/ledmatrix-cache/` (fallback) - `$TMPDIR/ledmatrix_cache/` (fallback)
```bash ```bash
# Find the cache dir actually in use # Find the cache dir actually in use
@@ -711,8 +726,9 @@ cache.clear_cache('display_on_demand_request')
cache.clear_cache('display_on_demand_processed_id') cache.clear_cache('display_on_demand_processed_id')
``` ```
> The actual public method is `clear_cache(key=None)` — there is no > `CacheManager` also has a `delete(key)` method — a thin wrapper over
> `delete()` method on `CacheManager`. > `clear_cache(key)` — so `cache.delete('display_on_demand_config')`
> works equally well.
### Cache Impact on Running Service ### Cache Impact on Running Service
@@ -730,7 +746,7 @@ The display controller automatically handles cleanup:
- **Config key**: Cleared when on-demand stops - **Config key**: Cleared when on-demand stops
- **State key**: Updated every display loop iteration - **State key**: Updated every display loop iteration
- **Request key**: Expires after 1 hour TTL (or after processing) - **Request key**: Expires after 1 hour TTL (or after processing)
- **Processed ID**: Expires after 5 minutes TTL - **Processed ID**: Expires after 1 hour TTL
--- ---
@@ -821,9 +837,6 @@ same shape as the example above.
### Testing ### Testing
```bash ```bash
# Run background service test
python test_background_service.py
# Check logs for background operations # Check logs for background operations
sudo journalctl -u ledmatrix -f | grep "background" sudo journalctl -u ledmatrix -f | grep "background"
``` ```
@@ -832,9 +845,10 @@ sudo journalctl -u ledmatrix -f | grep "background"
**View Statistics:** **View Statistics:**
```python ```python
from src.background_data_service import BackgroundDataService from src.background_data_service import get_background_service
from src.cache_manager import CacheManager
service = BackgroundDataService() service = get_background_service(CacheManager())
stats = service.get_statistics() stats = service.get_statistics()
print(f"Active tasks: {stats['active_tasks']}") print(f"Active tasks: {stats['active_tasks']}")
print(f"Completed: {stats['completed']}") print(f"Completed: {stats['completed']}")
@@ -875,6 +889,7 @@ from src.common.permission_utils import (
ensure_file_permissions, ensure_file_permissions,
get_config_file_mode, get_config_file_mode,
get_assets_file_mode, get_assets_file_mode,
get_assets_dir_mode,
get_plugin_file_mode, get_plugin_file_mode,
get_cache_dir_mode get_cache_dir_mode
) )
@@ -883,7 +898,10 @@ from src.common.permission_utils import (
ensure_directory_permissions(Path("assets/sports"), get_assets_dir_mode()) ensure_directory_permissions(Path("assets/sports"), get_assets_dir_mode())
# Set file permissions after writing # Set file permissions after writing
ensure_file_permissions(Path("config/config.json"), get_config_file_mode()) # (get_config_file_mode requires the file path — secrets files get a
# stricter mode than the main config)
config_path = Path("config/config.json")
ensure_file_permissions(config_path, get_config_file_mode(config_path))
``` ```
### When to Use Utilities ### When to Use Utilities
@@ -938,7 +956,7 @@ from src.common.permission_utils import ensure_file_permissions, get_config_file
config_path = Path("config/config.json") config_path = Path("config/config.json")
with open(config_path, 'w') as f: with open(config_path, 'w') as f:
json.dump(data, f) json.dump(data, f)
ensure_file_permissions(config_path, get_config_file_mode()) ensure_file_permissions(config_path, get_config_file_mode(config_path))
``` ```
**Pattern 3: Downloading Logo** **Pattern 3: Downloading Logo**
+2 -1
View File
@@ -190,7 +190,8 @@ def display(self, force_clear=False):
``` ```
LEDMatrix/ LEDMatrix/
├── plugins/ # Installed plugins ├── plugin-repos/ # Installed plugins (default; plugins/ is only
│ # for dev symlinks via scripts/dev/dev_plugin_setup.sh)
├── config/ ├── config/
│ ├── config.json # Main configuration │ ├── config.json # Main configuration
│ └── config_secrets.json # API keys and secrets │ └── config_secrets.json # API keys and secrets
+83 -74
View File
@@ -69,23 +69,24 @@ default configuration as it ships in the repo:
```json ```json
{ {
"pixel_outline": 0, "pixel_outline": 0,
"pixel_size": 5, "pixel_size": 16,
"pixel_style": "square", "pixel_style": "square",
"pixel_glow": 6, "pixel_glow": 6,
"display_adapter": "pygame", "display_adapter": "browser",
"allow_adapter_fallback": true,
"icon_path": null, "icon_path": null,
"emulator_title": null, "emulator_title": null,
"suppress_font_warnings": false, "suppress_font_warnings": false,
"suppress_adapter_load_errors": false,
"browser": { "browser": {
"_comment": "For use with the browser adapter only.", "_comment": "For use with the browser adapter only.",
"port": 8888, "port": 8888,
"target_fps": 24, "target_fps": 60,
"fps_display": false, "fps_display": false,
"quality": 70, "quality": 70,
"image_border": true, "image_border": true,
"debug_text": false, "debug_text": false,
"image_format": "JPEG" "image_format": "JPEG",
"open_immediately": false
}, },
"log_level": "info" "log_level": "info"
} }
@@ -96,13 +97,13 @@ default configuration as it ships in the repo:
| Option | Description | Default | Values | | Option | Description | Default | Values |
|--------|-------------|---------|--------| |--------|-------------|---------|--------|
| `pixel_outline` | Pixel border thickness | 0 | 0-5 | | `pixel_outline` | Pixel border thickness | 0 | 0-5 |
| `pixel_size` | Size of each pixel | 5 | 1-64 (816 is typical for testing) | | `pixel_size` | Size of each pixel | 16 | 1-64 (816 is typical for testing) |
| `pixel_style` | Pixel shape | "square" | "square", "circle" | | `pixel_style` | Pixel shape | "square" | "square", "circle" |
| `pixel_glow` | Glow effect intensity | 6 | 0-20 | | `pixel_glow` | Glow effect intensity | 6 | 0-20 |
| `display_adapter` | Display backend | "pygame" | "pygame", "browser" | | `display_adapter` | Display backend | "browser" | "browser", "pygame" |
| `allow_adapter_fallback` | Fall back to another adapter if the configured one fails to load | true | true/false |
| `emulator_title` | Window title | null | Any string | | `emulator_title` | Window title | null | Any string |
| `suppress_font_warnings` | Hide font warnings | false | true/false | | `suppress_font_warnings` | Hide font warnings | false | true/false |
| `suppress_adapter_load_errors` | Hide adapter errors | false | true/false |
### 3. Browser Adapter Configuration ### 3. Browser Adapter Configuration
@@ -111,18 +112,32 @@ When using the browser adapter, additional options are available:
| Option | Description | Default | | Option | Description | Default |
|--------|-------------|---------| |--------|-------------|---------|
| `port` | Web server port | 8888 | | `port` | Web server port | 8888 |
| `target_fps` | Target frames per second | 24 | | `target_fps` | Target frames per second | 60 |
| `fps_display` | Show FPS counter | false | | `fps_display` | Show FPS counter | false |
| `quality` | Image compression quality | 70 | | `quality` | Image compression quality | 70 |
| `image_border` | Show image border | true | | `image_border` | Show image border | true |
| `debug_text` | Show debug information | false | | `debug_text` | Show debug information | false |
| `image_format` | Image format | "JPEG" | | `image_format` | Image format | "JPEG" |
| `open_immediately` | Open the browser page automatically on start | false |
## Running the Emulator ## Running the Emulator
### 1. Set Environment Variable ### 1. Use the `-e` Flag (Recommended)
Enable emulator mode by setting the `EMULATOR` environment variable: `run.py` accepts exactly two flags: `-e`/`--emulator` and
`-d`/`--debug`.
```bash
python3 run.py -e
# With verbose logging
python3 run.py -e -d
```
### 2. Alternative: Set the Environment Variable
You can also enable emulator mode via the `EMULATOR` environment
variable:
**Windows (Command Prompt):** **Windows (Command Prompt):**
```cmd ```cmd
@@ -137,15 +152,6 @@ python run.py
``` ```
**Linux/macOS:** **Linux/macOS:**
```bash
export EMULATOR=true
python3 run.py
```
### 2. Alternative: Direct Python Execution
You can also run the emulator directly:
```bash ```bash
EMULATOR=true python3 run.py EMULATOR=true python3 run.py
``` ```
@@ -153,7 +159,8 @@ EMULATOR=true python3 run.py
### 3. Verify Emulator Mode ### 3. Verify Emulator Mode
When running in emulator mode, you should see: When running in emulator mode, you should see:
- A window displaying the LED matrix simulation - The emulated matrix — a web page at `http://localhost:8888` with the
default browser adapter, or a desktop window with the pygame adapter
- Console output indicating emulator mode - Console output indicating emulator mode
- No hardware initialization errors - No hardware initialization errors
@@ -161,7 +168,36 @@ When running in emulator mode, you should see:
LEDMatrix supports two display adapters for the emulator: LEDMatrix supports two display adapters for the emulator:
### 1. Pygame Adapter (Default) ### 1. Browser Adapter (Default)
The browser adapter runs a web server and displays the matrix as a web
page at `http://localhost:8888`. This is the adapter the shipped
`emulator_config.json` uses.
**Features:**
- Web-based interface
- Remote access capability
- Mobile-friendly
- Screenshot capture
**Configuration:**
```json
{
"display_adapter": "browser",
"browser": {
"port": 8888,
"target_fps": 60,
"quality": 70
}
}
```
**Usage:**
1. Start the emulator (`python3 run.py -e`)
2. Open browser to `http://localhost:8888`
3. View the LED matrix display
### 2. Pygame Adapter (Alternative)
The pygame adapter provides a native desktop window with real-time display. The pygame adapter provides a native desktop window with real-time display.
@@ -186,33 +222,6 @@ The pygame adapter provides a native desktop window with real-time display.
- `+/-` - Zoom in/out - `+/-` - Zoom in/out
- `R` - Reset zoom - `R` - Reset zoom
### 2. Browser Adapter
The browser adapter runs a web server and displays the matrix in a web browser.
**Features:**
- Web-based interface
- Remote access capability
- Mobile-friendly
- Screenshot capture
**Configuration:**
```json
{
"display_adapter": "browser",
"browser": {
"port": 8888,
"target_fps": 24,
"quality": 70
}
}
```
**Usage:**
1. Start the emulator with browser adapter
2. Open browser to `http://localhost:8888`
3. View the LED matrix display
## Troubleshooting ## Troubleshooting
### Common Issues ### Common Issues
@@ -299,17 +308,18 @@ Modify the display dimensions in your main config:
### 2. Plugin Development ### 2. Plugin Development
For plugin development with the emulator: `run.py` always runs the full rotation — it has no single-plugin flag.
To preview or check one plugin in isolation, use the dev tools:
```bash ```bash
# Enable emulator mode # Run the full display in emulator mode (optionally with debug logging)
export EMULATOR=true python3 run.py -e -d
# Run with specific plugin # Live single-plugin preview in the browser (port 5001)
python run.py --plugin my-plugin python3 scripts/dev_server.py
# Debug mode # Headless render/validation of one plugin
python run.py --debug python3 scripts/check_plugin.py --plugin my-plugin
``` ```
### 3. Performance Tuning ### 3. Performance Tuning
@@ -344,11 +354,10 @@ The emulator can work alongside the web interface:
```bash ```bash
# Terminal 1: Start emulator # Terminal 1: Start emulator
export EMULATOR=true python3 run.py -e
python run.py
# Terminal 2: Start web interface # Terminal 2: Start web interface (supported entry point)
python web_interface/app.py python3 web_interface/start.py
``` ```
Access the web interface at `http://localhost:5000` while the emulator runs. Access the web interface at `http://localhost:5000` while the emulator runs.
@@ -365,13 +374,14 @@ Access the web interface at `http://localhost:5000` while the emulator runs.
### 2. Plugin Testing ### 2. Plugin Testing
```bash ```bash
# Test specific plugin # Test a specific plugin (headless check)
export EMULATOR=true python3 scripts/check_plugin.py --plugin clock-simple
python run.py --plugin clock-simple
# Test all plugins # Preview a single plugin live in the browser (port 5001)
export EMULATOR=true python3 scripts/dev_server.py
python run.py --test-plugins
# Test the full rotation in the emulator
python3 run.py -e
``` ```
### 3. Configuration Management ### 3. Configuration Management
@@ -385,9 +395,8 @@ python run.py --test-plugins
### Basic Clock Display ### Basic Clock Display
```bash ```bash
# Start emulator with clock # Start emulator with clock enabled in config.json
export EMULATOR=true python3 run.py -e
python run.py
``` ```
### Sports Scores ### Sports Scores
@@ -395,16 +404,16 @@ python run.py
```bash ```bash
# Configure for sports display # Configure for sports display
# Edit config/config.json to enable sports plugins # Edit config/config.json to enable sports plugins
export EMULATOR=true python3 run.py -e
python run.py
``` ```
### Custom Text Display ### Custom Text Display
```bash ```bash
# Use text display plugin # Preview the text display plugin on its own
export EMULATOR=true python3 scripts/check_plugin.py --plugin text-display
python run.py --plugin text-display --text "Hello World" # or use the live dev preview server
python3 scripts/dev_server.py
``` ```
## Support ## Support
+41 -15
View File
@@ -21,18 +21,30 @@ This guide will help you set up your LEDMatrix display for the first time and ge
--- ---
## Quick Start (5 Minutes) ## Quick Start
### 1. First Boot ### 1. Install LEDMatrix
1. Insert the MicroSD card with LEDMatrix installed There is no prebuilt SD card image — you install LEDMatrix onto stock
2. Connect the LED matrix to your Raspberry Pi Raspberry Pi OS Lite yourself:
3. Plug in the power supply
4. Wait for the Pi to boot (about 60 seconds)
**Expected Behavior:** 1. Flash Raspberry Pi OS Lite to the MicroSD card (Raspberry Pi Imager)
2. Connect the LED matrix to your Raspberry Pi, insert the card, and
power on
3. SSH into the Pi and run the one-shot installer:
```bash
curl -fsSL https://raw.githubusercontent.com/ChuckBuilds/LEDMatrix/main/scripts/install/one-shot-install.sh | bash
```
or clone the repo and run `sudo ./first_time_install.sh` — see the
[README Installation Steps / Quick Install](../README.md#installation-steps)
for full details
**Expected Behavior after install:**
- LED matrix will light up - LED matrix will light up
- Display will show default plugins (clock, weather, etc.) - A fresh install ships only the bundled `starlark-apps` and
`web-ui-info` plugins — clock, weather, sports, etc. must be
installed from the Plugin Store (web UI → Plugin Manager) before
anything else displays
- Pi creates WiFi network "LEDMatrix-Setup" if not connected - Pi creates WiFi network "LEDMatrix-Setup" if not connected
### 2. Connect to WiFi ### 2. Connect to WiFi
@@ -73,7 +85,7 @@ You should see:
2. Set your matrix configuration: 2. Set your matrix configuration:
- **Rows**: 32 or 64 (match your hardware) - **Rows**: 32 or 64 (match your hardware)
- **Columns**: commonly 64 or 96; the web UI accepts any integer - **Columns**: commonly 64 or 96; the web UI accepts any integer
in the 16128 range, but 64 and 96 are the values the bundled in the 1128 range, but 64 and 96 are the values the bundled
panel hardware ships with panel hardware ships with
- **Chain Length**: Number of panels chained horizontally - **Chain Length**: Number of panels chained horizontally
- **Hardware Mapping**: usually `adafruit-hat-pwm` (with the PWM jumper - **Hardware Mapping**: usually `adafruit-hat-pwm` (with the PWM jumper
@@ -115,11 +127,16 @@ You can also install community plugins straight from a GitHub URL using the
1. Each installed plugin gets its own tab in the second navigation row 1. Each installed plugin gets its own tab in the second navigation row
2. Open that plugin's tab to edit its settings (favorite teams, API keys, 2. Open that plugin's tab to edit its settings (favorite teams, API keys,
update intervals, display duration, etc.) update intervals, etc.)
3. Click **Save** 3. Click **Save**
4. Restart the display service from **Overview** so the new settings take 4. Restart the display service from **Overview** so the new settings take
effect effect
**Note:** how long each plugin stays on screen is not set in the
plugin's own tab — use the **Rotation** tab's **Screen Durations**
section instead (saved to `display.display_durations` in
`config.json`).
**Example: Weather Plugin** **Example: Weather Plugin**
- Set your location (city, state, country) - Set your location (city, state, country)
- Add an API key from OpenWeatherMap (free signup) to - Add an API key from OpenWeatherMap (free signup) to
@@ -208,12 +225,14 @@ The fastest way to verify a plugin works without waiting for the rotation:
### Customize Your Display ### Customize Your Display
**Adjust display durations:** **Adjust display durations:**
- Each plugin's tab has a **Display Duration (seconds)** field — set how - Open the **Rotation** tab and use the **Screen Durations** section to
long that plugin stays on screen each rotation. set how long each plugin stays on screen per rotation (saved to
`display.display_durations`).
**Organize plugin order:** **Organize plugin order:**
- Use the **Plugin Manager** tab to enable/disable plugins. The display - The **Rotation** tab also has a drag-and-drop **Rotation Order** list
cycles through enabled plugins in the order they appear. (saved to `display.plugin_rotation_order`). Enable/disable plugins
from the **Plugin Manager** tab.
**Add more plugins:** **Add more plugins:**
- Check the **Plugin Store** section of **Plugin Manager** for new plugins. - Check the **Plugin Store** section of **Plugin Manager** for new plugins.
@@ -280,10 +299,14 @@ sudo journalctl -u ledmatrix-web -f
│ ├── config_secrets.json # API keys and secrets │ ├── config_secrets.json # API keys and secrets
│ └── wifi_config.json # WiFi settings │ └── wifi_config.json # WiFi settings
├── plugin-repos/ # Installed plugins (default location) ├── plugin-repos/ # Installed plugins (default location)
├── cache/ # Cached data
└── web_interface/ # Web interface files └── web_interface/ # Web interface files
``` ```
> Cached data does not live in the project directory — the cache manager
> uses the first writable location among `/var/cache/ledmatrix`,
> `~/.ledmatrix_cache`, `/opt/ledmatrix/cache`, and
> `$TMPDIR/ledmatrix_cache`.
> The plugin install location is configurable via > The plugin install location is configurable via
> `plugin_system.plugins_directory` in `config.json`. The default is > `plugin_system.plugins_directory` in `config.json`. The default is
> `plugin-repos/`. Plugin discovery (`PluginManager.discover_plugins()`) > `plugin-repos/`. Plugin discovery (`PluginManager.discover_plugins()`)
@@ -303,11 +326,14 @@ System tabs:
- WiFi Network selection and AP-mode setup - WiFi Network selection and AP-mode setup
- Schedule Power and dim schedules - Schedule Power and dim schedules
- Display Matrix hardware configuration - Display Matrix hardware configuration
- Rotation Rotation order (drag-and-drop) and screen durations
- Config Editor Raw config.json editor - Config Editor Raw config.json editor
- Backup & Restore Config backup and restore
- Fonts Upload and manage fonts - Fonts Upload and manage fonts
- Logs Real-time log viewing - Logs Real-time log viewing
- Cache Cached data inspection and cleanup - Cache Cached data inspection and cleanup
- Operation History Recent service operations - Operation History Recent service operations
- Tools System diagnostics, updates, dependencies, maintenance
Plugin tabs (second row): Plugin tabs (second row):
- Plugin Manager Browse the Plugin Store, install/enable plugins - Plugin Manager Browse the Plugin Store, install/enable plugins
+3 -2
View File
@@ -201,8 +201,9 @@ the mode selector for this plugin.
#### `get_vegas_segment_width() -> Optional[int]` #### `get_vegas_segment_width() -> Optional[int]`
For `FIXED_SEGMENT` plugins, the width in pixels of the segment they For `FIXED_SEGMENT` plugins, the number of *panels* the segment
occupy in the scroll. `None` lets the controller pick a default. occupies in the scroll (pixel width = panels × `single_panel_width`,
from `display.hardware.cols`). `None` uses the default of 1 panel.
> The full source for `BasePlugin` lives in > The full source for `BasePlugin` lives in
> `src/plugin_system/base_plugin.py`. If a method here disagrees with the > `src/plugin_system/base_plugin.py`. If a method here disagrees with the
+8 -6
View File
@@ -14,8 +14,10 @@ and [PLUGIN_DEVELOPMENT_GUIDE.md](PLUGIN_DEVELOPMENT_GUIDE.md).
**GitHub Store**: Discovery from `ledmatrix-plugins` registry plus **GitHub Store**: Discovery from `ledmatrix-plugins` registry plus
any GitHub URL any GitHub URL
**Plugin Location**: configured by `plugin_system.plugins_directory` **Plugin Location**: configured by `plugin_system.plugins_directory`
in `config.json` (default `plugin-repos/`; the loader also searches in `config.json` (default `plugin-repos/`). Plugin discovery scans
`plugins/` as a fallback) only this directory — there is no loader fallback to `plugins/`
(only Plugin Store operations and schema lookup additionally probe
`plugins/`)
## File Structure ## File Structure
@@ -109,7 +111,7 @@ git push -u origin main
git tag v1.0.0 git tag v1.0.0
git push origin v1.0.0 git push origin v1.0.0
# Submit to registry (PR to ChuckBuilds/ledmatrix-plugin-registry) # Submit to registry (PR to ChuckBuilds/ledmatrix-plugins)
``` ```
## Using Plugins ## Using Plugins
@@ -120,12 +122,12 @@ git push origin v1.0.0
2. **Install**: Click **Install** in the plugin's row 2. **Install**: Click **Install** in the plugin's row
3. **Configure**: open the plugin's tab in the second nav row 3. **Configure**: open the plugin's tab in the second nav row
4. **Enable/Disable**: toggle switch in the **Installed Plugins** list 4. **Enable/Disable**: toggle switch in the **Installed Plugins** list
5. **Reorder**: order is set by the position in `display_modes` / 5. **Reorder**: use the drag-and-drop **Rotation Order** list in the
plugin order; rearranging via drag-and-drop is not yet supported **Rotation** tab (saved to `display.plugin_rotation_order`)
### REST API ### REST API
The API is mounted at `/api/v3` (`web_interface/app.py:144`). The API is mounted at `/api/v3` (`web_interface/app.py:199`).
```bash ```bash
# Install plugin from the registry # Install plugin from the registry
+1 -1
View File
@@ -33,7 +33,7 @@ crashing) simply restores the built-in look.
## The render funnel ## The render funnel
Every sports scoreboard (baseball, football, basketball, hockey — anything Every sports scoreboard (baseball, football, basketball, hockey — anything
built on `src/base_classes/sports.py`) renders through exactly one seam: built on the `src/base_classes/sports/` package, `core.py`) renders through exactly one seam:
`SportsCore._render_game(game, force_clear)`. `SportsCore._render_game(game, force_clear)`.
1. The mode class's `display()` (live, `SportsUpcoming`, `SportsRecent`) 1. The mode class's `display()` (live, `SportsUpcoming`, `SportsRecent`)
+46 -52
View File
@@ -330,8 +330,8 @@ sudo systemctl cat ledmatrix-web | grep User
6. **Manually enable AP mode:** 6. **Manually enable AP mode:**
```bash ```bash
# Via API # Via API (the WiFi blueprint is mounted under /api/v3)
curl -X POST http://localhost:5000/api/wifi/ap/enable curl -X POST http://localhost:5000/api/v3/wifi/ap/enable
# Via Python # Via Python
python3 -c " python3 -c "
@@ -482,19 +482,19 @@ sudo systemctl cat ledmatrix-web | grep User
1. **Check plugin directory exists:** 1. **Check plugin directory exists:**
```bash ```bash
ls -ld plugins/plugin-id/ ls -ld plugin-repos/plugin-id/
``` ```
2. **Verify manifest.json:** 2. **Verify manifest.json:**
```bash ```bash
cat plugins/plugin-id/manifest.json cat plugin-repos/plugin-id/manifest.json
# Verify all required fields present # Verify all required fields present
``` ```
3. **Check dependencies installed:** 3. **Check dependencies installed:**
```bash ```bash
if [ -f plugins/plugin-id/requirements.txt ]; then if [ -f plugin-repos/plugin-id/requirements.txt ]; then
pip3 install --break-system-packages -r plugins/plugin-id/requirements.txt pip3 install --break-system-packages -r plugin-repos/plugin-id/requirements.txt
fi fi
``` ```
@@ -507,7 +507,7 @@ sudo systemctl cat ledmatrix-web | grep User
```bash ```bash
python3 -c " python3 -c "
import sys import sys
sys.path.insert(0, 'plugins/plugin-id') sys.path.insert(0, 'plugin-repos/plugin-id')
from manager import PluginClass from manager import PluginClass
print('Plugin imports successfully') print('Plugin imports successfully')
" "
@@ -523,12 +523,18 @@ sudo systemctl cat ledmatrix-web | grep User
**Solutions:** **Solutions:**
1. **Manual cache clearing:** 1. **Manual cache clearing:**
```bash
# Remove plugin-specific cache
rm -rf cache/plugin-id*
# Or remove all cache The cache does not live in the project directory. The cache manager
rm -rf cache/* uses the first writable location among `/var/cache/ledmatrix`,
`~/.ledmatrix_cache`, `/opt/ledmatrix/cache`, and
`$TMPDIR/ledmatrix_cache`. The easiest option is the helper script:
```bash
# Clear the cache with the helper script
sudo python3 scripts/utils/clear_cache.py
# Or remove files manually from the cache dir in use, e.g.:
sudo rm -rf /var/cache/ledmatrix/*
# Restart display # Restart display
sudo systemctl restart ledmatrix sudo systemctl restart ledmatrix
@@ -536,8 +542,8 @@ sudo systemctl cat ledmatrix-web | grep User
2. **Check cache permissions:** 2. **Check cache permissions:**
```bash ```bash
ls -ld cache/ ls -ld /var/cache/ledmatrix
sudo chown -R ledpi:ledpi cache/ sudo ./scripts/fix_perms/fix_cache_permissions.sh
``` ```
--- ---
@@ -772,11 +778,11 @@ nmcli device status
```bash ```bash
# Check file exists # Check file exists
ls -l config/config.json ls -l config/config.json
ls -l plugins/plugin-id/manifest.json ls -l plugin-repos/plugin-id/manifest.json
# Check directory structure # Check directory structure
ls -la web_interface/ ls -la web_interface/
ls -la plugins/ ls -la plugin-repos/
# Check file permissions # Check file permissions
ls -l config/config_secrets.json ls -l config/config_secrets.json
@@ -804,7 +810,7 @@ python3 -c "from src.wifi_manager import WiFiManager; print('OK')"
# Test plugin import # Test plugin import
python3 -c " python3 -c "
import sys import sys
sys.path.insert(0, 'plugins/plugin-id') sys.path.insert(0, 'plugin-repos/plugin-id')
from manager import PluginClass from manager import PluginClass
print('Plugin imports OK') print('Plugin imports OK')
" "
@@ -812,40 +818,29 @@ print('Plugin imports OK')
--- ---
## Service File Template ## Reinstalling Service Files
If your systemd service file is corrupted or missing, use this template: If a systemd service file is corrupted or missing, do NOT hand-write
one. The real unit files live in the repo's `systemd/` directory
```ini (`ledmatrix.service`, `ledmatrix-web.service`,
[Unit] `ledmatrix-wifi-monitor.service`) and contain a
Description=LEDMatrix Web Interface `__PROJECT_ROOT_DIR__` placeholder that the install scripts substitute
After=network.target with your actual checkout path:
[Service]
Type=simple
User=ledpi
Group=ledpi
WorkingDirectory=/home/ledpi/LEDMatrix
Environment="PYTHONUNBUFFERED=1"
ExecStart=/usr/bin/python3 /home/ledpi/LEDMatrix/web_interface/start.py
Restart=on-failure
RestartSec=5s
StandardOutput=journal
StandardError=journal
SyslogIdentifier=ledmatrix-web
[Install]
WantedBy=multi-user.target
```
Save to `/etc/systemd/system/ledmatrix-web.service` and run:
```bash ```bash
sudo systemctl daemon-reload # Reinstall the display service unit
sudo systemctl enable ledmatrix-web sudo ./scripts/install/install_service.sh
sudo systemctl start ledmatrix-web
# Reinstall the web interface service unit
sudo ./scripts/install/install_web_service.sh
``` ```
Note that `ledmatrix-web.service` runs as root via
`scripts/utils/start_web_conditionally.py` — root is needed for
system operations (service control, WiFi management), and the wrapper
honors the `web_display_autostart` config flag before actually
starting the web server.
--- ---
## Complete Diagnostic Script ## Complete Diagnostic Script
@@ -878,7 +873,7 @@ echo ""
echo "5. File Structure:" echo "5. File Structure:"
ls -la web_interface/ | head -10 ls -la web_interface/ | head -10
ls -la plugins/ | head -10 ls -la plugin-repos/ | head -10
echo "" echo ""
echo "6. Python Imports:" echo "6. Python Imports:"
@@ -954,12 +949,11 @@ sudo systemctl restart ledmatrix-web
# Reinstall WiFi monitor # Reinstall WiFi monitor
sudo ./scripts/install/install_wifi_monitor.sh sudo ./scripts/install/install_wifi_monitor.sh
# Recreate service files from templates # Recreate service files (substitutes __PROJECT_ROOT_DIR__ in systemd/ units)
sudo cp templates/ledmatrix.service /etc/systemd/system/ sudo ./scripts/install/install_service.sh
sudo cp templates/ledmatrix-web.service /etc/systemd/system/ sudo ./scripts/install/install_web_service.sh
# Reload and restart # Restart
sudo systemctl daemon-reload
sudo systemctl restart ledmatrix ledmatrix-web sudo systemctl restart ledmatrix ledmatrix-web
``` ```
+23 -18
View File
@@ -39,12 +39,18 @@ present:
- **WiFi** — Network selection and AP-mode setup - **WiFi** — Network selection and AP-mode setup
- **Schedule** — Power and dim schedules - **Schedule** — Power and dim schedules
- **Display** — Matrix hardware configuration (rows, cols, hardware - **Display** — Matrix hardware configuration (rows, cols, hardware
mapping, GPIO slowdown, brightness, PWM) mapping, GPIO slowdown, brightness, PWM) and Vegas Scroll Mode
settings
- **Rotation** — drag-and-drop **Rotation Order** list and per-plugin
**Screen Durations**
- **Config Editor** — Raw `config.json` editor with validation - **Config Editor** — Raw `config.json` editor with validation
- **Backup & Restore** — back up and restore your configuration
- **Fonts** — Upload and manage fonts - **Fonts** — Upload and manage fonts
- **Logs** — Real-time log streaming - **Logs** — Real-time log streaming
- **Cache** — Cached data inspection and cleanup - **Cache** — Cached data inspection and cleanup
- **Operation History** — Recent service operations - **Operation History** — Recent service operations
- **Tools** — system diagnostics, git & updates, Python dependencies,
maintenance, power supply, network radio, services, and plugin health
A second nav row holds plugin tabs: A second nav row holds plugin tabs:
@@ -111,6 +117,12 @@ Configure your LED matrix hardware:
- Dynamic Duration — global cap for plugins that extend their display - Dynamic Duration — global cap for plugins that extend their display
time based on content time based on content
**Vegas Scroll Mode:** the Display tab also has a full Vegas Scroll
Mode section — enable toggle, scroll speed, separator width, dynamic
duration, and related settings — so you can configure Vegas mode
entirely from the web UI without hand-editing JSON. See
[ADVANCED_FEATURES.md](ADVANCED_FEATURES.md) for what the options do.
Changes require **Restart Display Service** from the Overview tab. Changes require **Restart Display Service** from the Overview tab.
### Plugin Manager Tab ### Plugin Manager Tab
@@ -159,9 +171,10 @@ Manage fonts for your display:
- See font previews - See font previews
- Check font sizes and styles - Check font sizes and styles
**Plugin Font Overrides:** **Font Overrides:**
- Set custom fonts for specific plugins - Overrides are set per display *element* (e.g. a specific score or
- Override default font choices clock text element), not per plugin
- Override default font choices for individual elements
- Preview font changes - Preview font changes
**Delete Fonts:** **Delete Fonts:**
@@ -183,9 +196,11 @@ View real-time system logs:
- Filter by plugin or component - Filter by plugin or component
**Actions:** **Actions:**
- **Refresh**: Reload the log view
- **Clear**: Clear the current view - **Clear**: Clear the current view
- **Download**: Download logs for offline analysis - **Download**: Download logs for offline analysis
- **Pause**: Pause auto-scrolling - **Auto-scroll** checkbox: toggle automatic scrolling to the latest
entries
--- ---
@@ -248,7 +263,8 @@ The web interface uses Server-Sent Events (SSE) for real-time updates:
**Performance:** **Performance:**
- Minimal bandwidth usage - Minimal bandwidth usage
- Server-side rendering for fast load times - Server-side rendering for fast load times
- Progressive enhancement - works without JavaScript - The UI is built on Alpine.js and HTMX, so JavaScript must be enabled
in the browser
--- ---
@@ -267,17 +283,6 @@ The interface is fully responsive and works on mobile devices:
--- ---
## Keyboard Shortcuts
Use keyboard shortcuts for faster navigation:
- **Tab**: Navigate between form fields
- **Enter**: Submit forms
- **Esc**: Close modals
- **Ctrl+F**: Search in logs
---
## API Access ## API Access
The web interface is built on a REST API that you can access programmatically: The web interface is built on a REST API that you can access programmatically:
@@ -288,7 +293,7 @@ http://your-pi-ip:5000/api/v3
``` ```
The API blueprint mounts at `/api/v3` (see The API blueprint mounts at `/api/v3` (see
`web_interface/app.py:144`). All endpoints below are relative to that `web_interface/app.py:199`). All endpoints below are relative to that
base. base.
**Common Endpoints:** **Common Endpoints:**