Files
LEDMatrix/.cursorrules
Chuck 8b838ff366 docs: fix .cursorrules — the file Cursor auto-loads to learn the API
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>
2026-04-07 10:22:41 -04:00

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`