mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-04-10 21:03:01 +00:00
This is the file that Cursor reads to learn how plugin development works. Stale entries here directly mislead AI-assisted plugin authors on every new plugin. Several of the same bug patterns I've been fixing in the user-facing docs were here too. Display Manager section (highest impact) - "draw_image(image, x, y): Draw PIL Image" — that method doesn't exist on DisplayManager. Same bug already fixed in PLUGIN_API_REFERENCE.md, PLUGIN_DEVELOPMENT_GUIDE.md, ledmatrix-stocks/README.md, and .cursor/plugin_templates/QUICK_START.md. Removed the bullet and replaced it with a paragraph explaining the real pattern: paste onto display_manager.image directly, then update_display(). Includes the transparency-mask form. - Added the small_font/centered args to draw_text() since they're the ones that matter most for new plugin authors - Added draw_weather_icon since it's commonly used Cache Manager section - "delete(key): Remove cached value" — there's no delete() method on CacheManager. The real method is clear_cache(key=None) (also removes everything when called without args). Same bug as before. - Added get_cached_data_with_strategy and get_background_cached_data since contributors will hit these when working on sports plugins Plugin System Overview - "loaded from the plugins/ directory" — clarified that the default is plugin-repos/ (per config.template.json:130) with plugins/ as the dev fallback used by scripts/dev/dev_plugin_setup.sh Plugin Development Workflow - ./dev_plugin_setup.sh -> ./scripts/dev/dev_plugin_setup.sh (×2) - Manual setup step "Create directory in plugins/<plugin-id>/" -> plugin-repos/<plugin-id>/ as the canonical location - "Use emulator: python run.py --emulator or ./run_emulator.sh" — the --emulator flag doesn't exist; ./run_emulator.sh isn't at root (it lives at scripts/dev/run_emulator.sh). Replaced with the real options: scripts/dev_server.py for dev preview, or EMULATOR=true python3 run.py for the full emulator path. Configuration Management - "Reference secrets via config_secrets key in main config" — this is the same fictional reference syntax I just fixed in .cursor/plugins_guide.md. Verified in src/config_manager.py:162-172 that secrets are deep-merged into the main config; there's no separate reference field. Replaced with a clear explanation of the deep-merge approach. Code Organization - "plugins/<plugin-id>/" -> the canonical location is plugin-repos/<plugin-id>/ (or its dev-time symlink in plugins/) - "see plugins/hockey-scoreboard/ as reference" — the canonical source for example plugins is the ledmatrix-plugins repo. Updated the pointer. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
345 lines
13 KiB
Plaintext
345 lines
13 KiB
Plaintext
# LEDMatrix Plugin Development Rules
|
|
|
|
## Plugin System Overview
|
|
|
|
The LEDMatrix project uses a plugin-based architecture. All display
|
|
functionality (except core calendar) is implemented as plugins that are
|
|
dynamically loaded from the directory configured by
|
|
`plugin_system.plugins_directory` in `config.json` — the default is
|
|
`plugin-repos/` (per `config/config.template.json:130`), and the loader
|
|
also falls back to `plugins/` (used by `scripts/dev/dev_plugin_setup.sh`
|
|
for symlinks).
|
|
|
|
## Plugin Structure
|
|
|
|
### Required Files
|
|
- **manifest.json**: Plugin metadata, entry point, class name, dependencies
|
|
- **manager.py**: Main plugin class (must inherit from `BasePlugin`)
|
|
- **config_schema.json**: JSON schema for plugin configuration validation
|
|
- **requirements.txt**: Python dependencies (if any)
|
|
- **README.md**: Plugin documentation
|
|
|
|
### Plugin Class Requirements
|
|
- Must inherit from `src.plugin_system.base_plugin.BasePlugin`
|
|
- Must implement `update()` method for data fetching
|
|
- Must implement `display()` method for rendering
|
|
- Should implement `validate_config()` for configuration validation
|
|
- Optional: Override `has_live_content()` for live priority features
|
|
|
|
## Plugin Development Workflow
|
|
|
|
### 1. Creating a New Plugin
|
|
|
|
**Option A: Use dev_plugin_setup.sh (Recommended)**
|
|
```bash
|
|
# Link from GitHub
|
|
./scripts/dev/dev_plugin_setup.sh link-github <plugin-name>
|
|
|
|
# Link local repository
|
|
./scripts/dev/dev_plugin_setup.sh link <plugin-name> <path-to-repo>
|
|
```
|
|
|
|
**Option B: Manual Setup**
|
|
1. Create directory in `plugin-repos/<plugin-id>/` (or `plugins/<plugin-id>/`
|
|
if you're using the dev fallback location)
|
|
2. Add `manifest.json` with required fields
|
|
3. Create `manager.py` with plugin class
|
|
4. Add `config_schema.json` for configuration
|
|
5. Enable plugin in `config/config.json` under `"<plugin-id>": {"enabled": true}`
|
|
|
|
### 2. Plugin Configuration
|
|
|
|
Plugins are configured in `config/config.json`:
|
|
```json
|
|
{
|
|
"<plugin-id>": {
|
|
"enabled": true,
|
|
"display_duration": 15,
|
|
"live_priority": false,
|
|
"high_performance_transitions": false,
|
|
"transition": {
|
|
"type": "redraw",
|
|
"speed": 2,
|
|
"enabled": true
|
|
},
|
|
// ... plugin-specific config
|
|
}
|
|
}
|
|
```
|
|
|
|
### 3. Testing Plugins
|
|
|
|
**On Development Machine:**
|
|
- Run the dev preview server: `python3 scripts/dev_server.py` (then
|
|
open `http://localhost:5001`) — renders plugins in the browser
|
|
without running the full display loop
|
|
- Or run the full display in emulator mode:
|
|
`EMULATOR=true python3 run.py` (or `./scripts/dev/run_emulator.sh`).
|
|
There is no `--emulator` flag.
|
|
- Test plugin loading: Check logs for plugin discovery and loading
|
|
- Validate configuration: Ensure config matches `config_schema.json`
|
|
|
|
**On Raspberry Pi:**
|
|
- Deploy and test on actual hardware
|
|
- Monitor logs: `journalctl -u ledmatrix -f` (if running as service)
|
|
- Check plugin status in web interface
|
|
|
|
### 4. Plugin Development Best Practices
|
|
|
|
**Code Organization:**
|
|
- Keep plugin code in `plugin-repos/<plugin-id>/` (or its dev-time
|
|
symlink in `plugins/<plugin-id>/`)
|
|
- Use shared assets from `assets/` directory when possible
|
|
- Follow existing plugin patterns — canonical sources live in the
|
|
[`ledmatrix-plugins`](https://github.com/ChuckBuilds/ledmatrix-plugins)
|
|
repo (`plugins/hockey-scoreboard/`, `plugins/football-scoreboard/`,
|
|
`plugins/clock-simple/`, etc.)
|
|
- Place shared utilities in `src/common/` if reusable across plugins
|
|
|
|
**Configuration Management:**
|
|
- Use `config_schema.json` for validation
|
|
- Store secrets in `config/config_secrets.json` under the same plugin
|
|
id namespace as the main config — they're deep-merged into the main
|
|
config at load time (`src/config_manager.py:162-172`), so plugin
|
|
code reads them directly from `config.get(...)` like any other key
|
|
- There is no separate `config_secrets` reference field
|
|
- Validate all required fields in `validate_config()`
|
|
|
|
**Error Handling:**
|
|
- Use plugin's logger: `self.logger.info/error/warning()`
|
|
- Handle API failures gracefully
|
|
- Cache data to avoid excessive API calls
|
|
- Provide fallback displays when data unavailable
|
|
|
|
**Performance:**
|
|
- Use `cache_manager` for API response caching
|
|
- Implement background data fetching if needed
|
|
- Use `high_performance_transitions` for smoother animations
|
|
- Optimize rendering for Pi's limited resources
|
|
|
|
**Display Rendering:**
|
|
- Use `display_manager` for all drawing operations
|
|
- Support different display sizes (check `display_manager.width/height`)
|
|
- Use `apply_transition()` for smooth transitions between displays
|
|
- Clear display before rendering: `display_manager.clear()`
|
|
- Always call `display_manager.update_display()` after rendering
|
|
|
|
## Plugin API Reference
|
|
|
|
### BasePlugin Class
|
|
Located in: `src/plugin_system/base_plugin.py`
|
|
|
|
**Required Methods:**
|
|
- `update()`: Fetch/update data (called based on `update_interval` in manifest)
|
|
- `display(force_clear=False)`: Render plugin content
|
|
|
|
**Optional Methods:**
|
|
- `validate_config()`: Validate plugin configuration
|
|
- `has_live_content()`: Return True if plugin has live/urgent content
|
|
- `get_live_modes()`: Return list of modes for live priority
|
|
- `cleanup()`: Clean up resources on unload
|
|
- `on_config_change(new_config)`: Handle config updates
|
|
- `on_enable()`: Called when plugin enabled
|
|
- `on_disable()`: Called when plugin disabled
|
|
|
|
**Available Properties:**
|
|
- `self.plugin_id`: Plugin identifier
|
|
- `self.config`: Plugin configuration dict
|
|
- `self.display_manager`: Display manager instance
|
|
- `self.cache_manager`: Cache manager instance
|
|
- `self.plugin_manager`: Plugin manager reference
|
|
- `self.logger`: Plugin-specific logger
|
|
- `self.enabled`: Boolean enabled status
|
|
- `self.transition_manager`: Transition system (if available)
|
|
|
|
### Display Manager
|
|
Located in: `src/display_manager.py`
|
|
|
|
**Key Methods:**
|
|
- `clear()`: Clear the display
|
|
- `draw_text(text, x, y, color, font, small_font, centered)`: Draw text
|
|
- `update_display()`: Push the buffer to the physical display
|
|
- `draw_weather_icon(condition, x, y, size)`: Draw a weather icon
|
|
- `width`, `height`: Display dimensions
|
|
|
|
**Image rendering**: there is no `draw_image()` helper. Paste directly
|
|
onto the underlying PIL Image:
|
|
```python
|
|
self.display_manager.image.paste(pil_image, (x, y))
|
|
self.display_manager.update_display()
|
|
```
|
|
For transparency, paste with a mask: `image.paste(rgba, (x, y), rgba)`.
|
|
|
|
### Cache Manager
|
|
Located in: `src/cache_manager.py`
|
|
|
|
**Key Methods:**
|
|
- `get(key, max_age=300)`: Get cached value (returns None if missing/stale)
|
|
- `set(key, value, ttl=None)`: Cache a value
|
|
- `clear_cache(key=None)`: Remove a cache entry, or all entries if `key`
|
|
is omitted. There is no `delete()` method.
|
|
- `get_cached_data_with_strategy(key, data_type)`: Cache get with
|
|
data-type-aware TTL strategy
|
|
- `get_background_cached_data(key, sport_key)`: Cache get for the
|
|
background-fetch service path
|
|
|
|
## Plugin Manifest Schema
|
|
|
|
Required fields in `manifest.json`:
|
|
- `id`: Unique plugin identifier (matches directory name)
|
|
- `name`: Human-readable plugin name
|
|
- `version`: Semantic version (e.g., "1.0.0")
|
|
- `entry_point`: Python file (usually "manager.py")
|
|
- `class_name`: Plugin class name (must match class in entry_point)
|
|
- `display_modes`: Array of mode names this plugin provides
|
|
|
|
Common optional fields:
|
|
- `description`: Plugin description
|
|
- `author`: Plugin author
|
|
- `homepage`: Plugin homepage URL
|
|
- `category`: Plugin category (e.g., "sports", "weather")
|
|
- `tags`: Array of tags
|
|
- `update_interval`: Seconds between update() calls (default: 60)
|
|
- `default_duration`: Default display duration (default: 15)
|
|
- `requires`: Python version, display size requirements
|
|
- `config_schema`: Path to config schema file
|
|
- `api_requirements`: API dependencies and rate limits
|
|
|
|
## Plugin Loading Process
|
|
|
|
1. **Discovery**: PluginManager scans `plugins/` directory for directories containing `manifest.json`
|
|
2. **Validation**: Validates manifest structure and required fields
|
|
3. **Loading**: Imports plugin module and instantiates plugin class
|
|
4. **Configuration**: Loads plugin config from `config/config.json`
|
|
5. **Validation**: Calls `validate_config()` on plugin instance
|
|
6. **Registration**: Adds plugin to available modes and stores instance
|
|
7. **Enablement**: Calls `on_enable()` if plugin is enabled
|
|
|
|
## Common Plugin Patterns
|
|
|
|
### Sports Scoreboard Plugin
|
|
- Use `background_data_service.py` pattern for API fetching
|
|
- Implement live/recent/upcoming game modes
|
|
- Use `scoreboard_renderer.py` for consistent rendering
|
|
- Support team filtering and game filtering
|
|
- Use shared sports logos from `assets/sports/`
|
|
|
|
### Data Display Plugin
|
|
- Fetch data in `update()` method
|
|
- Cache API responses using `cache_manager`
|
|
- Render in `display()` method
|
|
- Handle API errors gracefully
|
|
- Provide configuration for refresh intervals
|
|
|
|
### Real-time Content Plugin
|
|
- Implement `has_live_content()` for live priority
|
|
- Use `get_live_modes()` to specify which modes are live
|
|
- Set `live_priority: true` in config to enable live takeover
|
|
- Update data frequently when live content exists
|
|
|
|
## Debugging Plugins
|
|
|
|
**Check Plugin Loading:**
|
|
- Review logs for plugin discovery messages
|
|
- Verify manifest.json syntax is valid JSON
|
|
- Check that class_name matches actual class name
|
|
- Ensure entry_point file exists and is importable
|
|
|
|
**Check Plugin Execution:**
|
|
- Add logging statements in `update()` and `display()`
|
|
- Use `self.logger` for plugin-specific logging
|
|
- Check cache_manager for cached data
|
|
- Verify display_manager is rendering correctly
|
|
|
|
**Common Issues:**
|
|
- Import errors: Check Python path and dependencies
|
|
- Config errors: Validate against config_schema.json
|
|
- Display issues: Check display dimensions and coordinate calculations
|
|
- Performance: Monitor CPU/memory usage on Pi
|
|
|
|
## Plugin Testing
|
|
|
|
**Unit Tests:**
|
|
- Test plugin class instantiation
|
|
- Test `update()` data fetching logic
|
|
- Test `display()` rendering logic
|
|
- Test `validate_config()` with various configs
|
|
- Mock `display_manager` and `cache_manager` for testing
|
|
|
|
**Integration Tests:**
|
|
- Test plugin loading via PluginManager
|
|
- Test plugin with actual config
|
|
- Test plugin with emulator display
|
|
- Test plugin with cache_manager
|
|
|
|
**Hardware Tests:**
|
|
- Test on Raspberry Pi with LED matrix
|
|
- Verify display rendering on actual hardware
|
|
- Test performance under load
|
|
- Test with other plugins enabled
|
|
|
|
## File Organization
|
|
|
|
```
|
|
plugins/
|
|
<plugin-id>/
|
|
manifest.json # Plugin metadata
|
|
manager.py # Main plugin class
|
|
config_schema.json # Config validation schema
|
|
requirements.txt # Python dependencies
|
|
README.md # Plugin documentation
|
|
# Plugin-specific files
|
|
data_manager.py
|
|
renderer.py
|
|
etc.
|
|
```
|
|
|
|
## Git Workflow for Plugins
|
|
|
|
**Plugin Development:**
|
|
- Plugins are typically separate repositories
|
|
- Use `dev_plugin_setup.sh` to link plugins for development
|
|
- Symlinks are used to connect plugin repos to `plugins/` directory
|
|
- Plugin repos follow naming: `ledmatrix-<plugin-name>`
|
|
|
|
**Branching:**
|
|
- Develop plugins in feature branches
|
|
- Follow project branching conventions
|
|
- Test plugins before merging to main
|
|
|
|
**Automatic Version Bumping:**
|
|
- **Automatic Version Management**: Version bumping is handled automatically via the pre-push git hook - no manual version bumping is required for normal development workflows
|
|
- **GitHub as Source of Truth**: Plugin store always fetches latest versions from GitHub (releases/tags/manifest/commit)
|
|
- **Pre-Push Hook**: Automatically bumps patch version and creates git tags when pushing code changes
|
|
- The hook is self-contained (no external dependencies) and works on any dev machine
|
|
- Installation: Copy the hook from LEDMatrix repo to your plugin repo:
|
|
```bash
|
|
# From your plugin repository directory
|
|
cp /path/to/LEDMatrix/scripts/git-hooks/pre-push-plugin-version .git/hooks/pre-push
|
|
chmod +x .git/hooks/pre-push
|
|
```
|
|
- Or use the installer script from the main LEDMatrix repo (one-time setup)
|
|
- The hook automatically:
|
|
1. Bumps the patch version (x.y.Z) in manifest.json when code changes are detected
|
|
2. Creates a git tag (v{version}) for the new version
|
|
3. Stages manifest.json for commit
|
|
- Skip auto-tagging: Set `SKIP_TAG=1` environment variable before pushing
|
|
- **Manual Version Bumping (Edge Cases Only)**: Manual version bumps are only needed in rare circumstances:
|
|
- CI/CD pipelines that bypass git hooks
|
|
- Forked repositories without the pre-push hook installed
|
|
- Major/minor version bumps (hook only handles patch versions)
|
|
- When skipping auto-tagging but still needing a version bump
|
|
- For manual bumps, use the standalone script: `scripts/bump_plugin_version.py`
|
|
- **Registry**: The plugin registry (plugins.json) stores only metadata (name, description, repo URL) - no versions
|
|
- **Version Priority**: Plugin store checks versions in this order: GitHub Releases → GitHub Tags → Manifest from branch → Git commit hash
|
|
|
|
## Resources
|
|
|
|
- Plugin System Docs: `docs/PLUGIN_ARCHITECTURE_SPEC.md`
|
|
- Plugin Examples: `plugins/hockey-scoreboard/`, `plugins/football-scoreboard/`
|
|
- Base Plugin: `src/plugin_system/base_plugin.py`
|
|
- Plugin Manager: `src/plugin_system/plugin_manager.py`
|
|
- Development Setup: `dev_plugin_setup.sh`
|
|
- Example Config: `dev_plugins.json.example`
|
|
|