Reviewed all 12 CodeRabbit comments on PR #306, verified each against the current code, and fixed the 11 valid ones. The 12th finding is a real code bug (cache_manager.delete() calls in api_helper.py and resource_monitor.py) that's already in the planned follow-up code-fix PR, so it stays out of this docs PR. Fixed: .cursor/plugins_guide.md, .cursor/README.md, .cursorrules - I claimed "there is no --emulator flag" in 3 places. Verified in run.py:19-20 that the -e/--emulator flag is defined and functional (it sets os.environ["EMULATOR"]="true" before the display imports). Other docs I didn't touch (.cursor/plugin_templates/QUICK_START.md, docs/PLUGIN_DEVELOPMENT_GUIDE.md) already use the flag correctly. Replaced all 3 wrong statements with accurate guidance that both forms work and explains the CLI flag's relationship to the env var. .cursorrules, docs/GETTING_STARTED.md, docs/WEB_INTERFACE_GUIDE.md, docs/PLUGIN_DEVELOPMENT_GUIDE.md - Four places claimed "the plugin loader also falls back to plugins/". Verified that PluginManager.discover_plugins() (src/plugin_system/plugin_manager.py:154) only scans the configured directory — no fallback. The fallback to plugins/ exists only in two narrower places: store_manager.py:1700-1718 (store install/update/uninstall operations) and schema_manager.py:70-80 (schema lookup for the web UI form generator). Rewrote all four mentions with the precise scope. Added a recommendation to set plugin_system.plugins_directory to "plugins" for the smoothest dev workflow with dev_plugin_setup.sh symlinks. docs/FONT_MANAGER.md - The "Status" warning told plugin authors to use display_manager.font_manager.resolve_font(...) as a workaround for loading plugin fonts. Verified in src/font_manager.py that resolve_font() takes a family name, not a file path — so the workaround as written doesn't actually work. Rewrote to tell authors to load the font directly with PIL or freetype-py in their plugin. - The same section said "the user-facing font override system in the Fonts tab still works for any element that's been registered via register_manager_font()". Verified in web_interface/blueprints/api_v3.py:5404-5428 that /api/v3/fonts/overrides is a placeholder implementation that returns empty arrays and contains "would integrate with the actual font system" comments — the Fonts tab does not have functional integration with register_manager_font() or the override system. Removed the false claim and added an explicit note that the tab is a placeholder. docs/ADVANCED_FEATURES.md:523 - The on-demand section said REST/UI calls write a request "into the cache manager (display_on_demand_config key)". Wrong — verified via grep that api_v3.py:1622 and :1687 write to display_on_demand_request, and display_on_demand_config is only written by the controller during activation (display_controller.py:1195, cleared at :1221). Corrected the key name and added controller file:line references so future readers can verify. docs/ADVANCED_FEATURES.md:803 - "Plugins using the background service" paragraph listed all scoreboard plugins but an orphaned "⏳ MLB (baseball)" bullet remained below from the old version of the section. Removed the orphan and added "baseball/MLB" to the inline list for clarity. web_interface/README.md - The POST /api/v3/system/action action list was incomplete. Verified in web_interface/app.py:1383,1386 that enable_autostart and disable_autostart are valid actions. Added both. - The Plugin Store section was missing GET /api/v3/plugins/store/github-status (verified at api_v3.py:3296). Added it. - The SSE line-range reference was app.py:607-615 but line 619 contains the "Exempt SSE streams from CSRF and add rate limiting" block that's semantically part of the same feature. Extended the range to 607-619. docs/GETTING_STARTED.md - Rows/Columns step said "Columns: 64 or 96 (match your hardware)". The web UI's validation accepts any integer in 16-128. Clarified that 64 and 96 are the common bundled-hardware values but the valid range is wider. Not addressed (out of scope for docs PR): - .cursorrules:184 CodeRabbit comment flagged the non-existent cache_manager.delete() calls in src/common/api_helper.py:287 and src/plugin_system/resource_monitor.py:343. These are real CODE bugs, not doc bugs, and they're the first item in the planned post-docs-refresh code-cleanup PR (see /home/chuck/.claude/plans/warm-imagining-river.md). The docs in this PR correctly state that delete() doesn't exist on CacheManager — the fix belongs in the follow-up code PR that either adds a delete() shim or updates the two callers. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
19 KiB
LEDMatrix Plugin Development Guide
This guide explains how to set up a development workflow for plugins that are maintained in separate Git repositories while still being able to test them within the LEDMatrix project.
Overview
When developing plugins in separate repositories, you need a way to:
- Test plugins within the LEDMatrix project
- Make changes and commit them back to the plugin repository
- Avoid git conflicts between LEDMatrix and plugin repositories
- Easily switch between development and production modes
The solution uses symbolic links to connect plugin repositories to the plugins/ directory, combined with a helper script to manage the linking process.
Plugin directory note: the dev workflow described here puts symlinks in
plugins/. The plugin loader's production default isplugin-repos/(set byplugin_system.plugins_directoryinconfig.json). Importantly, the main discovery path (PluginManager.discover_plugins()) only scans the configured directory — it does not fall back toplugins/. Two narrower paths do: the Plugin Store install/update logic instore_manager.py, andschema_manager.get_schema_path()(which the web UI form generator uses to findconfig_schema.json). That's why plugins installed via the Plugin Store still work even with symlinks inplugins/, but your own dev plugin won't appear in the rotation until you either move it toplugin-repos/or changeplugin_system.plugins_directorytopluginsin the General tab of the web UI. The latter is the smoother dev setup.
Quick Start
1. Link a Plugin from GitHub
The easiest way to link a plugin that's already on GitHub:
./scripts/dev/dev_plugin_setup.sh link-github music
This will:
- Clone
https://github.com/ChuckBuilds/ledmatrix-music.gitto~/.ledmatrix-dev-plugins/ledmatrix-music - Create a symbolic link from
plugins/musicto the cloned repository - Validate that the plugin has a proper
manifest.json
2. Link a Local Plugin Repository
If you already have a plugin repository cloned locally:
./scripts/dev/dev_plugin_setup.sh link music ../ledmatrix-music
This creates a symlink from plugins/music to your local repository path.
3. Check Status
See which plugins are linked and their git status:
./scripts/dev/dev_plugin_setup.sh status
4. Work on Your Plugin
cd plugins/music # Actually editing the linked repository
# Make your changes
git add .
git commit -m "feat: add new feature"
git push origin main
5. Update Plugins
Pull latest changes from remote:
# Update all linked plugins
./scripts/dev/dev_plugin_setup.sh update
# Or update a specific plugin
./scripts/dev/dev_plugin_setup.sh update music
6. Unlink When Done
Remove the symlink (repository is preserved):
./scripts/dev/dev_plugin_setup.sh unlink music
Detailed Commands
link <plugin-name> <repo-path>
Links a local plugin repository to the plugins directory.
Arguments:
plugin-name: The name of the plugin (will be the directory name inplugins/)repo-path: Path to the plugin repository (absolute or relative)
Example:
./scripts/dev/dev_plugin_setup.sh link football-scoreboard ../ledmatrix-football-scoreboard
Notes:
- The script validates that the repository contains a
manifest.jsonfile - If a plugin directory already exists, you'll be prompted to replace it
- The repository path can be absolute or relative
link-github <plugin-name> [repo-url]
Clones a plugin from GitHub and links it.
Arguments:
plugin-name: The name of the plugin (will be the directory name inplugins/)repo-url: (Optional) Full GitHub repository URL. If omitted, constructs from pattern:https://github.com/ChuckBuilds/ledmatrix-<plugin-name>.git
Examples:
# Auto-construct URL from plugin name
./scripts/dev/dev_plugin_setup.sh link-github music
# Use explicit URL
./scripts/dev/dev_plugin_setup.sh link-github stocks https://github.com/ChuckBuilds/ledmatrix-stocks.git
# Link from a different GitHub user
./scripts/dev/dev_plugin_setup.sh link-github custom-plugin https://github.com/OtherUser/custom-plugin.git
Notes:
- Repositories are cloned to
~/.ledmatrix-dev-plugins/by default (configurable) - If the repository already exists, it will be updated with
git pullinstead of re-cloning - The cloned repository is preserved when you unlink the plugin
unlink <plugin-name>
Removes the symlink for a plugin.
Arguments:
plugin-name: The name of the plugin to unlink
Example:
./scripts/dev/dev_plugin_setup.sh unlink music
Notes:
- Only removes the symlink, does NOT delete the repository
- Your work and git history are preserved in the repository location
list
Lists all plugins in the plugins/ directory and shows their status.
Example:
./scripts/dev/dev_plugin_setup.sh list
Output:
- ✓ Green checkmark: Plugin is symlinked (development mode)
- ○ Yellow circle: Plugin is a regular directory (production/installed mode)
- Shows the source path for symlinked plugins
- Shows git status (branch, clean/dirty) for linked repos
status
Shows detailed status of all linked plugins.
Example:
./scripts/dev/dev_plugin_setup.sh status
Shows:
- Link status (working/broken)
- Repository path
- Git branch
- Remote URL
- Git status (clean, uncommitted changes, ahead/behind remote)
- Summary of all plugins
update [plugin-name]
Updates plugin(s) by running git pull in their repositories.
Arguments:
plugin-name: (Optional) Specific plugin to update. If omitted, updates all linked plugins.
Examples:
# Update all linked plugins
./scripts/dev/dev_plugin_setup.sh update
# Update specific plugin
./scripts/dev/dev_plugin_setup.sh update music
Configuration
Custom Development Directory
By default, GitHub repositories are cloned to ~/.ledmatrix-dev-plugins/. You can customize this by creating a dev_plugins.json file:
{
"dev_plugins_dir": "/path/to/your/dev/plugins",
"github_user": "ChuckBuilds",
"github_pattern": "ledmatrix-",
"plugins": {
"music": {
"source": "github",
"url": "https://github.com/ChuckBuilds/ledmatrix-music.git",
"branch": "main"
}
}
}
Configuration options:
dev_plugins_dir: Where to clone GitHub repositories (default:~/.ledmatrix-dev-plugins)github_user: Default GitHub username for auto-constructing URLsgithub_pattern: Pattern for repository names (default:ledmatrix-)plugins: Plugin definitions (optional, for future auto-discovery features)
Note: Copy dev_plugins.json.example to dev_plugins.json and customize it. The dev_plugins.json file is git-ignored.
Development Workflow
Typical Development Session
-
Link your plugin for development:
./scripts/dev/dev_plugin_setup.sh link-github music -
Test in LEDMatrix:
# Run LEDMatrix with your plugin python run.py -
Make changes:
cd plugins/music # Edit files... # Test changes... -
Commit to plugin repository:
cd plugins/music # This is actually your repo git add . git commit -m "feat: add new feature" git push origin main -
Update from remote (if needed):
./scripts/dev/dev_plugin_setup.sh update music -
When done developing:
./scripts/dev/dev_plugin_setup.sh unlink music
Working with Multiple Plugins
You can have multiple plugins linked simultaneously:
./scripts/dev/dev_plugin_setup.sh link-github music
./scripts/dev/dev_plugin_setup.sh link-github stocks
./scripts/dev/dev_plugin_setup.sh link-github football-scoreboard
# Check status of all
./scripts/dev/dev_plugin_setup.sh status
# Update all at once
./scripts/dev/dev_plugin_setup.sh update
Switching Between Development and Production
Development mode: Plugins are symlinked to your repositories
- Edit files directly in
plugins/<name> - Changes are in the plugin repository
- Git operations work normally
Production mode: Plugins are installed normally
- Plugins are regular directories (installed via plugin store or manually)
- Can't edit directly (would need to edit in place or re-install)
- Use
unlinkto remove symlink if you want to switch back to installed version
Best Practices
1. Keep Repositories Outside LEDMatrix
The script clones GitHub repositories to ~/.ledmatrix-dev-plugins/ by default, which is outside the LEDMatrix directory. This:
- Avoids git conflicts
- Keeps plugin repos separate from LEDMatrix repo
- Makes it easy to manage multiple plugin repositories
2. Use Descriptive Commit Messages
When committing changes in your plugin repository, use clear commit messages following the project's conventions:
git commit -m "feat(music): add album art support"
git commit -m "fix(stocks): resolve API timeout issue"
3. Test Before Committing
Always test your plugin changes in LEDMatrix before committing:
# Make changes
cd plugins/music
# ... edit files ...
# Test in LEDMatrix
cd ../..
python run.py
# If working, commit
cd plugins/music
git add .
git commit -m "feat: new feature"
4. Keep Plugins Updated
Regularly update your linked plugins to get the latest changes:
./scripts/dev/dev_plugin_setup.sh update
5. Check Status Regularly
Before starting work, check the status of your linked plugins:
./scripts/dev/dev_plugin_setup.sh status
This helps you:
- See if you have uncommitted changes
- Check if you're behind the remote
- Identify any broken symlinks
Troubleshooting
Plugin Not Discovered by LEDMatrix
If LEDMatrix doesn't discover your linked plugin:
-
Check the symlink exists:
ls -la plugins/your-plugin-name -
Verify manifest.json exists:
ls plugins/your-plugin-name/manifest.json -
Check PluginManager logs:
- LEDMatrix logs should show plugin discovery
- Look for errors related to the plugin
Broken Symlink
If a symlink is broken (target repository was moved or deleted):
-
Check status:
./scripts/dev/dev_plugin_setup.sh status -
Unlink and re-link:
./scripts/dev/dev_plugin_setup.sh unlink plugin-name ./scripts/dev/dev_plugin_setup.sh link-github plugin-name
Git Conflicts
If you have conflicts when updating:
-
Manually resolve in the plugin repository:
cd ~/.ledmatrix-dev-plugins/ledmatrix-music git pull # Resolve conflicts... git add . git commit -
Or use the update command:
./scripts/dev/dev_plugin_setup.sh update music
Plugin Directory Already Exists
If you try to link a plugin but the directory already exists:
-
Check if it's already linked:
./scripts/dev/dev_plugin_setup.sh list -
If it's a symlink to the same location, you're done
-
If it's a regular directory or different symlink:
- The script will prompt you to replace it
- Or manually backup:
mv plugins/plugin-name plugins/plugin-name.backup
Advanced Usage
Linking Plugins from Different GitHub Users
./scripts/dev/dev_plugin_setup.sh link-github custom-plugin https://github.com/OtherUser/custom-plugin.git
Using a Custom Development Directory
Create dev_plugins.json:
{
"dev_plugins_dir": "/home/user/my-dev-plugins"
}
Combining Local and GitHub Plugins
You can mix local and GitHub plugins:
# Link from GitHub
./scripts/dev/dev_plugin_setup.sh link-github music
# Link local repository
./scripts/dev/dev_plugin_setup.sh link custom-plugin ../my-custom-plugin
Integration with Plugin Store
The development workflow is separate from the plugin store installation:
- Plugin Store: Installs plugins to
plugins/as regular directories - Development Setup: Links plugin repositories as symlinks
If you install a plugin via the store, you can still link it for development:
# Store installs to plugins/music (regular directory)
# Link for development (will prompt to replace)
./scripts/dev/dev_plugin_setup.sh link-github music
When you unlink, the directory is removed. If you want to switch back to the store version, re-install it via the plugin store.
API Reference
When developing plugins, you'll need to use the APIs provided by the LEDMatrix system:
- Plugin API Reference - Complete reference for Display Manager, Cache Manager, and Plugin Manager methods
- Advanced Plugin Development - Advanced patterns, examples, and best practices
- Developer Quick Reference - Quick reference for common developer tasks
Key APIs for Plugin Developers
Display Manager (self.display_manager):
clear(),update_display()- Core display operationsdraw_text()- Text rendering. For images, paste directly ontodisplay_manager.image(a PIL Image) and callupdate_display(); there is nodraw_image()helper method.draw_weather_icon(),draw_sun(),draw_cloud()- Weather iconsget_text_width(),get_font_height()- Text utilitiesset_scrolling_state(),defer_update()- Scrolling state management
Cache Manager (self.cache_manager):
get(),set(),delete()- Basic cachingget_cached_data_with_strategy()- Advanced caching with strategiesget_background_cached_data()- Background service caching
Plugin Manager (self.plugin_manager):
get_plugin(),get_all_plugins()- Access other pluginsget_plugin_info()- Get plugin information
See PLUGIN_API_REFERENCE.md for complete documentation.
3rd Party Plugin Development
Want to create and share your own plugin? Here's everything you need to know.
Getting Started
-
Review the documentation:
- Plugin Architecture Spec - System architecture
- Plugin API Reference - Available methods
- Advanced Plugin Development - Patterns and examples
-
Start with a template:
- Use the Hello World plugin as a starting point
- Or fork an existing plugin and modify it
-
Follow the plugin structure:
your-plugin/ ├── manifest.json # Required: Plugin metadata ├── manager.py # Required: Plugin class ├── config_schema.json # Recommended: Configuration schema ├── requirements.txt # Optional: Python dependencies └── README.md # Recommended: User documentation
Plugin Requirements
Your plugin must:
-
Inherit from BasePlugin:
from src.plugin_system.base_plugin import BasePlugin class MyPlugin(BasePlugin): def update(self): # Fetch data pass def display(self, force_clear=False): # Render display pass -
Include manifest.json with required fields:
{ "id": "my-plugin", "name": "My Plugin", "version": "1.0.0", "class_name": "MyPlugin", "entry_point": "manager.py", "display_modes": ["my_plugin"], "compatible_versions": [">=2.0.0"] } -
Match class name: The class name in
manager.pymust matchclass_namein manifest
Testing Your Plugin
-
Test locally:
# Link your plugin for development ./scripts/dev/dev_plugin_setup.sh link your-plugin /path/to/your-plugin # Run LEDMatrix with emulator python run.py --emulator -
Test on hardware: Deploy to Raspberry Pi and test on actual LED matrix
-
Use mocks for unit testing: See Advanced Plugin Development
Versioning Best Practices
- Use semantic versioning:
MAJOR.MINOR.PATCH(e.g.,1.2.3) - Automatic version bumping: Use the pre-push git hook for automatic patch version bumps
- Manual versioning: Only needed for major/minor bumps or special cases
- GitHub as source of truth: Plugin store fetches versions from GitHub releases/tags/manifest
See the Git Workflow rules for version management details.
Submitting to Official Registry
To have your plugin added to the official plugin store:
-
Ensure quality:
- Plugin works reliably
- Well-documented (README.md)
- Follows best practices
- Tested on Raspberry Pi hardware
-
Create GitHub repository:
- Repository name:
ledmatrix-<plugin-name> - Public repository
- Proper README.md with installation instructions
- Repository name:
-
Contact maintainers:
- Open a GitHub issue in the ledmatrix-plugins repository
- Or reach out on Discord: https://discord.gg/uW36dVAtcT
- Include: Repository URL, plugin description, why it's useful
-
Review process:
- Code review for quality and security
- Testing on Raspberry Pi hardware
- Documentation review
- If approved, added to official registry
Plugin Store Integration Requirements
For your plugin to work well in the plugin store:
- GitHub repository: Must be publicly accessible on GitHub
- Releases or tags: Recommended for version tracking
- README.md: Clear installation and configuration instructions
- config_schema.json: Recommended for web UI configuration
- manifest.json: Required with all required fields
- requirements.txt: If your plugin has Python dependencies
Distribution Options
-
Official Registry (Recommended):
- Listed in default plugin store
- Automatic updates
- Verified badge
- Requires approval
-
Custom Repository:
- Host your own plugin repository
- Users can install via "Install from GitHub" in web UI
- Full control over distribution
-
Direct Installation:
- Users can clone and install manually
- Good for development/testing
Best Practices for 3rd Party Plugins
- Documentation: Include comprehensive README.md
- Configuration: Provide config_schema.json for web UI
- Error handling: Graceful failures with clear error messages
- Logging: Use plugin logger for debugging
- Testing: Test on actual Raspberry Pi hardware
- Versioning: Follow semantic versioning
- Dependencies: Minimize external dependencies
- Performance: Optimize for Pi's limited resources
See Also
- Plugin Architecture Specification - Complete system specification
- Plugin API Reference - Complete API documentation
- Advanced Plugin Development - Advanced patterns and examples
- Plugin Quick Reference - Quick development reference
- Plugin Configuration Guide - Configuration setup
- Plugin Store User Guide - Using the plugin store