chore: remove Cursor IDE tooling, consolidate its guidance into CLAUDE.md

The maintainer no longer uses Cursor. .cursorrules, .cursorignore and the
.cursor/ tree (rules, plugin templates, a parallel 751-line plugins
guide) are removed; measurement showed near-zero literal overlap risk —
the canonical content already lives in docs/. Unique guidance worth
keeping moved before deletion:

- CLAUDE.md gains the dev workflow (dev_plugin_setup.sh, dev_server.py,
  run.py -e, check_plugin.py), the plugin-secrets namespacing contract,
  and the no-draw_image()/paste-onto-PIL pitfall.
- PLUGIN_DEVELOPMENT_GUIDE.md absorbs the plugin version-management
  rules (pre-push hook install, SKIP_TAG, version resolution order) that
  its own text previously linked out to .cursorrules for.
- The one completed plan doc (.cursor/plans/) is archived to
  docs/archive/ per the docs policy rather than deleted.

One of the deleted rule files (sports-managers.mdc) targeted
src/*_managers.py globs that have matched nothing since the plugin
migration.

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:50:35 +00:00
parent 97336b805e
commit cc1db66662
23 changed files with 30 additions and 2693 deletions
-145
View File
@@ -1,145 +0,0 @@
# Cursor Helper Files for LEDMatrix Plugin Development
This directory contains Cursor-specific helper files to assist with plugin development in the LEDMatrix project.
## Files Overview
### `.cursorrules`
Comprehensive rules file that Cursor uses to understand plugin development patterns, best practices, and workflows. This file is automatically loaded by Cursor and helps guide AI-assisted development.
### `plugins_guide.md`
Detailed guide covering:
- Plugin system overview
- Creating new plugins
- Running plugins (emulator and hardware)
- Loading and configuring plugins
- Development workflow
- Testing strategies
- Troubleshooting
### `plugin_templates/`
Template files for quick plugin creation:
- `manifest.json.template` - Plugin metadata template
- `manager.py.template` - Plugin class template
- `config_schema.json.template` - Configuration schema template
- `README.md.template` - Plugin documentation template
- `requirements.txt.template` - Dependencies template
- `QUICK_START.md` - Quick start guide for using templates
## Quick Reference
### Creating a New Plugin
1. **Using templates** (recommended):
```bash
# See QUICK_START.md in plugin_templates/
cd plugins
mkdir my-plugin
cd my-plugin
cp ../../.cursor/plugin_templates/*.template .
# Edit files, replacing PLUGIN_ID and other placeholders
```
2. **Using dev_plugin_setup.sh**:
```bash
# Link from GitHub
./scripts/dev/dev_plugin_setup.sh link-github my-plugin
# Link local repo
./scripts/dev/dev_plugin_setup.sh link my-plugin /path/to/repo
```
### Running the Display
```bash
# Emulator mode (development, no hardware required)
python3 run.py --emulator
# (equivalent: EMULATOR=true python3 run.py)
# Hardware (production, requires the rpi-rgb-led-matrix submodule built)
python3 run.py
# As a systemd service
sudo systemctl start ledmatrix
# Dev preview server (renders plugins to a browser without running run.py)
python3 scripts/dev_server.py # then open http://localhost:5001
```
The `-e`/`--emulator` CLI flag is defined in `run.py:19-20` and
sets `os.environ["EMULATOR"] = "true"` before any display imports,
which `src/display_manager.py:2` then reads to switch between the
hardware and emulator backends.
### Managing Plugins
```bash
# List plugins
./scripts/dev/dev_plugin_setup.sh list
# Check status
./scripts/dev/dev_plugin_setup.sh status
# Update plugin(s)
./scripts/dev/dev_plugin_setup.sh update [plugin-name]
# Unlink plugin
./scripts/dev/dev_plugin_setup.sh unlink <plugin-name>
```
## Using These Files with Cursor
### `.cursorrules`
Cursor automatically reads this file to understand:
- Plugin structure and requirements
- Development workflows
- Best practices
- Common patterns
- API reference
When asking Cursor to help with plugins, it will use this context to provide better assistance.
### Plugin Templates
Use templates when creating new plugins:
1. Copy templates from `.cursor/plugin_templates/`
2. Replace placeholders (PLUGIN_ID, PluginClassName, etc.)
3. Customize for your plugin's needs
4. Follow the guide in `plugins_guide.md`
### Documentation
Refer to `plugins_guide.md` for:
- Detailed explanations
- Troubleshooting steps
- Best practices
- Examples and patterns
## Plugin Development Workflow
1. **Plan**: Determine plugin functionality and requirements
2. **Create**: Use templates or dev_plugin_setup.sh to create plugin structure
3. **Develop**: Implement plugin logic following BasePlugin interface
4. **Test**: Test with emulator first, then on hardware
5. **Configure**: Add plugin config to config/config.json
6. **Iterate**: Refine based on testing and feedback
## Resources
- **Plugin System**: `src/plugin_system/`
- **Base Plugin**: `src/plugin_system/base_plugin.py`
- **Plugin Manager**: `src/plugin_system/plugin_manager.py`
- **Example Plugins**: see the
[`ledmatrix-plugins`](https://github.com/ChuckBuilds/ledmatrix-plugins)
repo for canonical sources (e.g. `plugins/hockey-scoreboard/`,
`plugins/football-scoreboard/`). Installed plugins land in
`plugin-repos/` (default) or `plugins/` (dev fallback).
- **Architecture Docs**: `docs/PLUGIN_ARCHITECTURE_SPEC.md`
- **Development Setup**: `scripts/dev/dev_plugin_setup.sh`
## Getting Help
1. Check `plugins_guide.md` for detailed documentation
2. Review `.cursorrules` for development patterns
3. Look at existing plugins for examples
4. Check logs for error messages
5. Review plugin system code in `src/plugin_system/`
-247
View File
@@ -1,247 +0,0 @@
# Quick Start: Creating a New Plugin
This guide will help you create a new plugin using the templates in `.cursor/plugin_templates/`.
## Step 1: Create Plugin Directory
```bash
cd /path/to/LEDMatrix
mkdir -p plugins/my-plugin
cd plugins/my-plugin
```
## Step 2: Copy Templates
```bash
# Copy all template files
cp ../../.cursor/plugin_templates/manifest.json.template ./manifest.json
cp ../../.cursor/plugin_templates/manager.py.template ./manager.py
cp ../../.cursor/plugin_templates/config_schema.json.template ./config_schema.json
cp ../../.cursor/plugin_templates/README.md.template ./README.md
cp ../../.cursor/plugin_templates/requirements.txt.template ./requirements.txt
```
## Step 3: Customize Files
### manifest.json
Replace placeholders:
- `PLUGIN_ID``my-plugin` (lowercase, use hyphens)
- `Plugin Name` → Your plugin's display name
- `PluginClassName``MyPlugin` (PascalCase)
- Update description, author, homepage, etc.
### manager.py
Replace placeholders:
- `PluginClassName``MyPlugin` (must match manifest)
- Implement `_fetch_data()` method
- Implement `_render_content()` method
- Add any custom validation in `validate_config()`
### config_schema.json
Customize:
- Update description
- Add/remove configuration properties
- Set default values
- Add validation rules
### README.md
Replace placeholders:
- `PLUGIN_ID``my-plugin`
- `Plugin Name` → Your plugin's name
- Fill in features, installation, configuration sections
### requirements.txt
Add your plugin's dependencies:
```txt
requests>=2.28.0
pillow>=9.0.0
```
## Step 4: Enable Plugin
Edit `config/config.json`:
```json
{
"my-plugin": {
"enabled": true,
"display_duration": 15
}
}
```
## Step 5: Test Plugin
### Test with Emulator
```bash
cd /path/to/LEDMatrix
python run.py --emulator
```
### Check Plugin Loading
Look for logs like:
```
[INFO] Discovered 1 plugin(s)
[INFO] Loaded plugin: my-plugin v1.0.0
[INFO] Added plugin mode: my-plugin
```
### Test Plugin Display
The plugin should appear in the display rotation. Check logs for any errors.
## Step 6: Develop and Iterate
1. Edit `manager.py` to implement your plugin logic
2. Test with emulator: `python run.py --emulator`
3. Check logs for errors
4. Iterate until working correctly
## Step 7: Test on Hardware (Optional)
When ready, test on Raspberry Pi:
```bash
# Deploy to Pi
rsync -avz plugins/my-plugin/ pi@raspberrypi:/path/to/LEDMatrix/plugins/my-plugin/
# Or if using git
ssh pi@raspberrypi "cd /path/to/LEDMatrix/plugins/my-plugin && git pull"
# Restart service
ssh pi@raspberrypi "sudo systemctl restart ledmatrix"
```
## Common Customizations
### Adding API Integration
1. Add API key to `config_schema.json`:
```json
{
"api_key": {
"type": "string",
"description": "API key for service"
}
}
```
2. Implement API call in `_fetch_data()`:
```python
import requests
def _fetch_data(self):
response = requests.get(
"https://api.example.com/data",
headers={"Authorization": f"Bearer {self.api_key}"}
)
return response.json()
```
3. Store API key in `config/config_secrets.json`:
```json
{
"my-plugin": {
"api_key": "your-secret-key"
}
}
```
### Adding Image Rendering
There is no `draw_image()` helper on `DisplayManager`. To render an
image, paste it directly onto the underlying PIL `Image`
(`display_manager.image`) and then call `update_display()`:
```python
def _render_content(self):
# Load and paste image onto the display canvas
image = Image.open("assets/logo.png").convert("RGB")
self.display_manager.image.paste(image, (0, 0))
# Draw text overlay
self.display_manager.draw_text(
"Text",
x=10, y=20,
color=(255, 255, 255)
)
self.display_manager.update_display()
```
For transparency, paste with a mask:
```python
icon = Image.open("assets/icon.png").convert("RGBA")
self.display_manager.image.paste(icon, (5, 5), icon)
```
### Adding Live Priority
1. Enable in config:
```json
{
"my-plugin": {
"live_priority": true
}
}
```
2. Implement `has_live_content()`:
```python
def has_live_content(self) -> bool:
return self.data and self.data.get("is_live", False)
```
3. Override `get_live_modes()` if needed:
```python
def get_live_modes(self) -> list:
return ["my_plugin_live_mode"]
```
## Troubleshooting
### Plugin Not Loading
- Check `manifest.json` syntax (must be valid JSON)
- Verify `entry_point` file exists
- Ensure `class_name` matches class name in manager.py
- Check for import errors in logs
### Configuration Errors
- Validate config against `config_schema.json`
- Check required fields are present
- Verify data types match schema
### Display Issues
- Check display dimensions: `display_manager.width`, `display_manager.height`
- Verify coordinates are within bounds
- Ensure `update_display()` is called
- Test with emulator first
## Next Steps
- Review existing plugins for patterns:
- `plugins/hockey-scoreboard/` - Sports scoreboard example
- `plugins/ledmatrix-music/` - Real-time data example
- `plugins/ledmatrix-stocks/` - Data display example
- Read full documentation:
- `.cursor/plugins_guide.md` - Comprehensive guide
- `docs/PLUGIN_ARCHITECTURE_SPEC.md` - Architecture details
- `.cursorrules` - Development rules
- Check plugin system code:
- `src/plugin_system/base_plugin.py` - Base class
- `src/plugin_system/plugin_manager.py` - Plugin manager
-156
View File
@@ -1,156 +0,0 @@
# Plugin Name
Brief description of what this plugin does.
## Features
- Feature 1
- Feature 2
- Feature 3
## Installation
1. Link the plugin to your LEDMatrix installation:
```bash
cd /path/to/LEDMatrix
./scripts/dev/dev_plugin_setup.sh link-github PLUGIN_ID
```
Or for local development:
```bash
./scripts/dev/dev_plugin_setup.sh link PLUGIN_ID /path/to/plugin/repo
```
2. Install dependencies:
```bash
pip install -r plugins/PLUGIN_ID/requirements.txt
```
3. Configure the plugin in `config/config.json`:
```json
{
"PLUGIN_ID": {
"enabled": true,
"display_duration": 15
}
}
```
**Note:** API keys and other sensitive credentials must be stored in `config/config_secrets.json`, not in `config/config.json`.
4. Store API keys in `config/config_secrets.json`:
```json
{
"PLUGIN_ID": {
"api_key": "your-secret-api-key"
}
}
```
## Configuration
### Required Settings
- `enabled` (boolean): Enable or disable the plugin
- `api_key` (string): API key for external service (if required)
### Optional Settings
- `display_duration` (number): How long to display this plugin (default: 15 seconds)
- `refresh_interval` (integer): How often to refresh data in seconds (default: 60)
- `live_priority` (boolean): Enable live priority takeover (default: false)
## Display Modes
This plugin provides the following display modes:
- `PLUGIN_ID`: Main display mode
## API Requirements
This plugin requires:
- **API Name**: Description of API requirements
- URL: https://api.example.com
- Rate Limit: X requests per minute
- Authentication: API key required
## Development
### Running Tests
```bash
cd plugins/PLUGIN_ID
python test_PLUGIN_ID.py
```
### Testing with Emulator
```bash
cd /path/to/LEDMatrix
python run.py --emulator
```
### Debugging
Enable debug logging in `config/config.json`:
```json
{
"logging": {
"level": "DEBUG"
}
}
```
Check logs:
```bash
# On Raspberry Pi (if running as service)
journalctl -u ledmatrix -f
# Direct execution
python run.py
```
## Troubleshooting
### Plugin Not Loading
1. Check that `manifest.json` exists and is valid
2. Verify `entry_point` file exists
3. Check that `class_name` matches the class in manager.py
4. Review logs for import errors
### Configuration Errors
1. Validate config against `config_schema.json`
2. Check required fields are present
3. Verify data types match schema
### API Errors
1. Verify API key is correct
2. Check API rate limits
3. Review network connectivity
4. Check API service status
## License
[License information]
## Author
Your Name
## Links
- GitHub: https://github.com/username/ledmatrix-PLUGIN_ID
- Documentation: [Link to docs]
- Issues: https://github.com/username/ledmatrix-PLUGIN_ID/issues
@@ -1,44 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"title": "Plugin Configuration Schema",
"description": "Configuration schema for Plugin Name",
"properties": {
"enabled": {
"type": "boolean",
"default": true,
"description": "Enable or disable this plugin"
},
"display_duration": {
"type": "number",
"default": 15,
"minimum": 1,
"maximum": 300,
"description": "How long to display this plugin in seconds"
},
"live_priority": {
"type": "boolean",
"default": false,
"description": "Enable live priority takeover when plugin has live content"
},
"refresh_interval": {
"type": "integer",
"default": 60,
"minimum": 1,
"description": "How often to refresh data in seconds"
},
"api_key": {
"type": "string",
"description": "API key for external service (store in config_secrets.json)",
"default": ""
},
"custom_setting": {
"type": "string",
"description": "Example custom setting - replace with your plugin's settings",
"default": "default_value"
}
},
"required": ["enabled"],
"additionalProperties": false
}
@@ -1,226 +0,0 @@
"""
Plugin Name
Brief description of what this plugin does.
API Version: 1.0.0
"""
from src.plugin_system.base_plugin import BasePlugin
from PIL import Image
from typing import Dict, Any, Optional
import logging
import time
class PluginClassName(BasePlugin):
"""
Plugin class that inherits from BasePlugin.
This plugin demonstrates the basic structure and common patterns
for LEDMatrix plugins.
"""
def __init__(
self,
plugin_id: str,
config: Dict[str, Any],
display_manager,
cache_manager,
plugin_manager,
):
"""Initialize the plugin."""
super().__init__(plugin_id, config, display_manager, cache_manager, plugin_manager)
# Initialize plugin-specific data
self.data = None
self.last_update_time = None
# Load configuration values
self.api_key = config.get("api_key", "")
self.refresh_interval = config.get("refresh_interval", 60)
self.logger.info(f"Plugin {plugin_id} initialized")
def update(self) -> None:
"""
Fetch/update data for this plugin.
This method is called periodically based on update_interval
specified in the manifest. Use cache_manager to avoid
excessive API calls.
"""
cache_key = f"{self.plugin_id}_data"
# Check cache first
cached = self.cache_manager.get(cache_key, max_age=self.refresh_interval)
if cached:
self.data = cached
self.logger.debug("Using cached data")
return
try:
# Fetch new data
self.data = self._fetch_data()
# Cache the data
self.cache_manager.set(cache_key, self.data, ttl=self.refresh_interval)
self.last_update_time = time.time()
self.logger.info("Data updated successfully")
except Exception as e:
self.logger.error(f"Failed to update data: {e}")
# Use cached data if available, even if expired
# Use a very large max_age (1 year) to effectively bypass expiration for fallback
expired_cached = self.cache_manager.get(cache_key, max_age=31536000)
if expired_cached:
self.data = expired_cached
self.logger.warning("Using expired cache due to update failure")
def display(self, force_clear: bool = False) -> None:
"""
Render this plugin's display.
Args:
force_clear: If True, clear display before rendering
"""
if force_clear:
self.display_manager.clear()
# Check if we have data to display
if not self.data:
self._display_error("No data available")
return
try:
# Render plugin content
self._render_content()
# Update the display
self.display_manager.update_display()
except Exception as e:
self.logger.error(f"Display error: {e}")
self._display_error("Display error")
def _fetch_data(self) -> Dict[str, Any]:
"""
Fetch data from external source.
Returns:
Dictionary containing fetched data
"""
# TODO: Implement data fetching logic
# Example:
# import requests
# response = requests.get("https://api.example.com/data",
# headers={"Authorization": f"Bearer {self.api_key}"})
# return response.json()
# Placeholder
return {
"message": "Hello, World!",
"timestamp": time.time()
}
def _render_content(self) -> None:
"""Render the plugin content on the display."""
# Get display dimensions
width = self.display_manager.width
height = self.display_manager.height
# Example: Draw text
text = self.data.get("message", "No data")
x = 5
y = height // 2
self.display_manager.draw_text(
text,
x=x,
y=y,
color=(255, 255, 255) # White
)
# Example: Draw image
# if hasattr(self, 'logo_image'):
# self.display_manager.draw_image(
# self.logo_image,
# x=0,
# y=0
# )
def _display_error(self, message: str) -> None:
"""Display an error message."""
self.display_manager.clear()
width = self.display_manager.width
height = self.display_manager.height
self.display_manager.draw_text(
message,
x=5,
y=height // 2,
color=(255, 0, 0) # Red
)
self.display_manager.update_display()
def validate_config(self) -> bool:
"""
Validate plugin configuration.
Returns:
True if config is valid, False otherwise
"""
# Call parent validation first
if not super().validate_config():
return False
# Add custom validation
# Example: Check for required API key
# if self.config.get("require_api_key", True):
# if not self.api_key:
# self.logger.error("API key is required but not provided")
# return False
return True
def has_live_content(self) -> bool:
"""
Check if plugin has live content to display.
Override this method to enable live priority features.
Returns:
True if plugin has live content, False otherwise
"""
# Example: Check if there's live data
# return self.data and self.data.get("is_live", False)
return False
def get_info(self) -> Dict[str, Any]:
"""
Return plugin info for display in web UI.
Returns:
Dictionary with plugin information
"""
info = super().get_info()
# Add plugin-specific info
info.update({
"data_available": self.data is not None,
"last_update": self.last_update_time,
# Add more info as needed
})
return info
def cleanup(self) -> None:
"""Cleanup resources when plugin is unloaded."""
# Clean up any resources (threads, connections, etc.)
# Example:
# if hasattr(self, 'api_client'):
# self.api_client.close()
super().cleanup()
@@ -1,55 +0,0 @@
{
"id": "PLUGIN_ID",
"name": "Plugin Name",
"version": "1.0.0",
"author": "Your Name",
"description": "Brief description of what this plugin does",
"homepage": "https://github.com/username/ledmatrix-PLUGIN_ID",
"entry_point": "manager.py",
"class_name": "PluginClassName",
"category": "custom",
"tags": ["custom", "example"],
"icon": "fas fa-icon-name",
"compatible_versions": [">=2.0.0"],
"min_ledmatrix_version": "2.0.0",
"max_ledmatrix_version": "3.0.0",
"requires": {
"python": ">=3.9",
"display_size": {
"min_width": 64,
"min_height": 32
}
},
"config_schema": "config_schema.json",
"assets": {
"logos": "Optional: Description of asset requirements"
},
"update_interval": 60,
"default_duration": 15,
"display_modes": [
"PLUGIN_ID"
],
"api_requirements": [
{
"name": "API Name",
"required": false,
"description": "Description of API requirements",
"url": "https://api.example.com",
"rate_limit": "Rate limit information"
}
],
"download_url_template": "https://github.com/username/ledmatrix-PLUGIN_ID/archive/refs/tags/v{version}.zip",
"versions": [
{
"released": "2025-01-01",
"version": "1.0.0",
"ledmatrix_min_version": "2.0.0"
}
],
"last_updated": "2025-01-01",
"stars": 0,
"downloads": 0,
"verified": false,
"screenshot": ""
}
@@ -1,13 +0,0 @@
# Plugin Dependencies
# Add your plugin's Python dependencies here
# Example dependencies (uncomment and modify as needed):
# requests>=2.28.0
# pillow>=9.0.0
# python-dateutil>=2.8.0
# Note: Core LEDMatrix dependencies are already available:
# - PIL/Pillow (for image handling)
# - Core plugin system classes
# - Display manager, cache manager, config manager
@@ -1,136 +0,0 @@
"""
Test file for Plugin Name plugin.
This file provides example unit tests for your plugin.
Run tests with: python -m pytest test_manager.py
Or: python test_manager.py
"""
import unittest
import sys
from pathlib import Path
# Add project root to path
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
from src.plugin_system.testing import PluginTestCase
from manager import PluginClassName
class TestPluginClassName(PluginTestCase):
"""Test cases for PluginClassName plugin."""
def setUp(self):
"""Set up test fixtures."""
super().setUp()
# Update plugin_id to match the plugin being tested
self.plugin_id = 'PLUGIN_ID'
# Create plugin instance
self.plugin = self.create_plugin_instance(
PluginClassName,
plugin_id='PLUGIN_ID',
config=self.get_mock_config()
)
def test_plugin_initialization(self):
"""Test that plugin initializes correctly."""
self.assert_plugin_initialized(self.plugin)
self.assertTrue(self.plugin.enabled)
def test_config_validation(self):
"""Test configuration validation."""
# Valid config should pass
self.assertTrue(self.plugin.validate_config())
# Test with invalid config if applicable
# invalid_config = self.get_mock_config(enabled='not-a-boolean')
# invalid_plugin = self.create_plugin_instance(
# PluginClassName,
# config=invalid_config
# )
# self.assertFalse(invalid_plugin.validate_config())
def test_update_method(self):
"""Test the update() method."""
# Reset mocks
self.cache_manager.reset()
# Call update
self.plugin.update()
# Assertions
# Example: Check that cache was used
# self.assert_cache_get('PLUGIN_ID_data')
# Example: Check that data was fetched and cached
# self.assert_cache_set('PLUGIN_ID_data')
def test_display_method(self):
"""Test the display() method."""
# Ensure plugin has data (call update first if needed)
# self.plugin.update()
# Call display
self.plugin.display(force_clear=True)
# Assertions
self.assert_display_cleared()
self.assert_display_updated()
# Example: Check that text was drawn
# self.assert_text_drawn("Expected Text")
# Example: Check that image was drawn
# self.assert_image_drawn()
def test_display_without_data(self):
"""Test display() behavior when no data is available."""
# Clear any cached data
self.cache_manager.reset()
# Call display
self.plugin.display()
# Should handle gracefully (no exceptions)
# May show error message or fallback content
self.assert_display_updated()
def test_get_display_duration(self):
"""Test display duration configuration."""
duration = self.plugin.get_display_duration()
self.assertIsInstance(duration, (int, float))
self.assertGreater(duration, 0)
# Test with custom duration
custom_config = self.get_mock_config(display_duration=30.0)
custom_plugin = self.create_plugin_instance(
PluginClassName,
config=custom_config
)
self.assertEqual(custom_plugin.get_display_duration(), 30.0)
def test_enable_disable(self):
"""Test plugin enable/disable functionality."""
self.assertTrue(self.plugin.enabled)
self.plugin.on_disable()
self.assertFalse(self.plugin.enabled)
self.plugin.on_enable()
self.assertTrue(self.plugin.enabled)
def test_config_change(self):
"""Test configuration change handling."""
new_config = self.get_mock_config(display_duration=20.0)
self.plugin.on_config_change(new_config)
self.assertEqual(self.plugin.config.get('display_duration'), 20.0)
if __name__ == '__main__':
unittest.main()
-751
View File
@@ -1,751 +0,0 @@
# LEDMatrix Plugin Development Guide
This guide provides comprehensive instructions for creating, running, and loading plugins in the LEDMatrix project.
## Table of Contents
1. [Plugin System Overview](#plugin-system-overview)
2. [Creating a New Plugin](#creating-a-new-plugin)
3. [Running Plugins](#running-plugins)
4. [Loading Plugins](#loading-plugins)
5. [Plugin Development Workflow](#plugin-development-workflow)
6. [Testing Plugins](#testing-plugins)
7. [Troubleshooting](#troubleshooting)
---
## Plugin System Overview
The LEDMatrix project uses a plugin-based architecture where all display functionality (except core calendar) is implemented as plugins. Plugins are dynamically loaded from the `plugins/` directory and integrated into the display rotation.
### Plugin Architecture
```
LEDMatrix Core
├── Plugin Manager (discovers, loads, manages plugins)
├── Display Manager (handles LED matrix rendering)
├── Cache Manager (data persistence)
├── Config Manager (configuration management)
└── Plugins/ (plugin directory)
├── plugin-1/
├── plugin-2/
└── ...
```
### Plugin Lifecycle
1. **Discovery**: PluginManager scans `plugins/` for directories with `manifest.json`
2. **Loading**: Plugin module is imported and class is instantiated
3. **Configuration**: Plugin config is loaded from `config/config.json`
4. **Validation**: `validate_config()` is called to verify configuration
5. **Registration**: Plugin is added to available display modes
6. **Execution**: `update()` is called periodically, `display()` is called during rotation
---
## Creating a New Plugin
### Method 1: Using dev_plugin_setup.sh (Recommended)
This method is best for plugins stored in separate Git repositories.
#### From GitHub Repository
```bash
# Link a plugin from GitHub (auto-detects URL)
./scripts/dev/dev_plugin_setup.sh link-github <plugin-name>
# Example: Link hockey-scoreboard plugin
./scripts/dev/dev_plugin_setup.sh link-github hockey-scoreboard
# With custom URL
./scripts/dev/dev_plugin_setup.sh link-github <plugin-name> https://github.com/user/repo.git
```
The script will:
- Clone the repository to `~/.ledmatrix-dev-plugins/` (or configured directory)
- Create a symlink in `plugins/<plugin-name>/` pointing to the cloned repo
- Validate the plugin structure
#### From Local Repository
```bash
# Link a local plugin repository
./scripts/dev/dev_plugin_setup.sh link <plugin-name> <path-to-repo>
# Example: Link a local plugin
./scripts/dev/dev_plugin_setup.sh link my-plugin ../ledmatrix-my-plugin
```
### Method 2: Manual Plugin Creation
1. **Create Plugin Directory**
```bash
mkdir -p plugins/my-plugin
cd plugins/my-plugin
```
2. **Create manifest.json**
```json
{
"id": "my-plugin",
"name": "My Plugin",
"version": "1.0.0",
"author": "Your Name",
"description": "Description of what this plugin does",
"entry_point": "manager.py",
"class_name": "MyPlugin",
"category": "custom",
"tags": ["custom", "example"],
"display_modes": ["my_plugin"],
"update_interval": 60,
"default_duration": 15,
"requires": {
"python": ">=3.9"
},
"config_schema": "config_schema.json"
}
```
3. **Create manager.py**
```python
from src.plugin_system.base_plugin import BasePlugin
from PIL import Image
import logging
class MyPlugin(BasePlugin):
"""My custom plugin implementation."""
def update(self):
"""Fetch/update data for this plugin."""
# Fetch data from API, files, etc.
# Use self.cache_manager for caching
cache_key = f"{self.plugin_id}_data"
cached = self.cache_manager.get(cache_key, max_age=3600)
if cached:
self.data = cached
return
# Fetch new data
self.data = self._fetch_data()
self.cache_manager.set(cache_key, self.data)
def display(self, force_clear=False):
"""Render this plugin's display."""
if force_clear:
self.display_manager.clear()
# Render content using display_manager
self.display_manager.draw_text(
"Hello, World!",
x=10, y=15,
color=(255, 255, 255)
)
self.display_manager.update_display()
def _fetch_data(self):
"""Fetch data from external source."""
# Implement your data fetching logic
return {"message": "Hello, World!"}
def validate_config(self):
"""Validate plugin configuration."""
# Check required config fields
if not super().validate_config():
return False
# Add custom validation
required_fields = ['api_key'] # Example
for field in required_fields:
if field not in self.config:
self.logger.error(f"Missing required field: {field}")
return False
return True
```
4. **Create config_schema.json**
```json
{
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"default": true,
"description": "Enable or disable this plugin"
},
"display_duration": {
"type": "number",
"default": 15,
"minimum": 1,
"description": "How long to display this plugin (seconds)"
},
"api_key": {
"type": "string",
"description": "API key for external service"
}
},
"required": ["enabled"]
}
```
5. **Create requirements.txt** (if needed)
```
requests>=2.28.0
pillow>=9.0.0
```
6. **Create README.md**
Document your plugin's functionality, configuration options, and usage.
---
## Running Plugins
### Development Mode (Emulator)
Run the LEDMatrix system with emulator for plugin testing:
```bash
# Using run.py
python run.py --emulator
# Using emulator script
./run_emulator.sh
```
The emulator will:
- Load all enabled plugins
- Display plugin content in a window (simulating LED matrix)
- Show logs for plugin loading and execution
- Allow testing without Raspberry Pi hardware
### Production Mode (Raspberry Pi)
Run on actual Raspberry Pi hardware:
```bash
# Direct execution
python run.py
# As systemd service
sudo systemctl start ledmatrix
sudo systemctl status ledmatrix
sudo journalctl -u ledmatrix -f # View logs
```
### Plugin-Specific Testing
Test individual plugin loading:
```python
# test_my_plugin.py
from src.plugin_system.plugin_manager import PluginManager
from src.config_manager import ConfigManager
from src.display_manager import DisplayManager
from src.cache_manager import CacheManager
# Initialize managers
config_manager = ConfigManager()
config = config_manager.load_config()
display_manager = DisplayManager(config)
cache_manager = CacheManager()
# Initialize plugin manager
plugin_manager = PluginManager(
plugins_dir="plugins",
config_manager=config_manager,
display_manager=display_manager,
cache_manager=cache_manager
)
# Discover and load plugin
plugins = plugin_manager.discover_plugins()
print(f"Discovered plugins: {plugins}")
if "my-plugin" in plugins:
if plugin_manager.load_plugin("my-plugin"):
plugin = plugin_manager.get_plugin("my-plugin")
plugin.update()
plugin.display()
print("Plugin loaded and displayed successfully!")
else:
print("Failed to load plugin")
```
---
## Loading Plugins
### Enabling Plugins
Plugins are enabled/disabled in `config/config.json`:
```json
{
"my-plugin": {
"enabled": true,
"display_duration": 15,
"api_key": "your-api-key-here"
}
}
```
### Plugin Configuration Structure
Each plugin has its own section in `config/config.json`:
```json
{
"<plugin-id>": {
"enabled": true, // Enable/disable plugin
"display_duration": 15, // Display duration in seconds
"live_priority": false, // Enable live priority takeover
"high_performance_transitions": false, // Use 120 FPS transitions
"transition": { // Transition configuration
"type": "redraw", // Transition type
"speed": 2, // Transition speed
"enabled": true // Enable transitions
},
// ... plugin-specific configuration
}
}
```
### Secrets Management
Store sensitive data (API keys, tokens) in `config/config_secrets.json`
under the same plugin id you use in `config/config.json`:
```json
{
"my-plugin": {
"api_key": "secret-api-key-here"
}
}
```
At load time, the config manager deep-merges `config_secrets.json` into
the main config (verified at `src/config_manager.py:162-172`). So in
your plugin's code:
```python
class MyPlugin(BasePlugin):
def __init__(self, plugin_id, config, display_manager, cache_manager, plugin_manager):
super().__init__(plugin_id, config, display_manager, cache_manager, plugin_manager)
self.api_key = config.get("api_key") # already merged from secrets
```
There is no separate `config_secrets` reference field — just put the
secret value under the same plugin namespace and read it from the
merged config.
### Plugin Discovery
Plugins are automatically discovered when:
- Directory exists in `plugins/`
- Directory contains `manifest.json`
- Manifest has required fields (`id`, `entry_point`, `class_name`)
Check discovered plugins:
```bash
# Using dev_plugin_setup.sh
./scripts/dev/dev_plugin_setup.sh list
# Output shows:
# ✓ plugin-name (symlink)
# → /path/to/repo
# ✓ Git repo is clean (branch: main)
```
### Plugin Status
Check plugin status and git information:
```bash
./scripts/dev/dev_plugin_setup.sh status
# Output shows:
# ✓ plugin-name
# Path: /path/to/repo
# Branch: main
# Remote: https://github.com/user/repo.git
# Status: Clean and up to date
```
---
## Plugin Development Workflow
### 1. Initial Setup
```bash
# Create or clone plugin repository
git clone https://github.com/user/ledmatrix-my-plugin.git
cd ledmatrix-my-plugin
# Link to LEDMatrix project
cd /path/to/LEDMatrix
./scripts/dev/dev_plugin_setup.sh link my-plugin ../ledmatrix-my-plugin
```
### 2. Development Cycle
1. **Edit plugin code** in linked repository
2. **Test with the dev preview server**:
`python3 scripts/dev_server.py` (then open `http://localhost:5001`).
Or run the full display in emulator mode with
`python3 run.py --emulator` (or equivalently
`EMULATOR=true python3 run.py`). The `-e`/`--emulator` CLI flag is
defined in `run.py:19-20` and sets the same `EMULATOR` environment
variable internally.
3. **Check logs** for errors or warnings
4. **Update configuration** in `config/config.json` if needed
5. **Iterate** until plugin works correctly
### 3. Testing on Hardware
```bash
# Deploy to Raspberry Pi
rsync -avz plugins/my-plugin/ ledpi@your-pi-ip:/path/to/LEDMatrix/plugins/my-plugin/
# Or if using git, pull on Pi
ssh ledpi@your-pi-ip "cd /path/to/LEDMatrix/plugins/my-plugin && git pull"
# Restart service
ssh ledpi@your-pi-ip "sudo systemctl restart ledmatrix"
```
### 4. Updating Plugins
```bash
# Update single plugin from git
./scripts/dev/dev_plugin_setup.sh update my-plugin
# Update all linked plugins
./scripts/dev/dev_plugin_setup.sh update
```
### 5. Unlinking Plugins
```bash
# Remove symlink (preserves repository)
./scripts/dev/dev_plugin_setup.sh unlink my-plugin
```
---
## Testing Plugins
### Unit Testing
Create test files in plugin directory:
```python
# plugins/my-plugin/test_my_plugin.py
import unittest
from unittest.mock import Mock, MagicMock
from manager import MyPlugin
class TestMyPlugin(unittest.TestCase):
def setUp(self):
self.config = {"enabled": True}
self.display_manager = Mock()
self.cache_manager = Mock()
self.plugin_manager = Mock()
self.plugin = MyPlugin(
plugin_id="my-plugin",
config=self.config,
display_manager=self.display_manager,
cache_manager=self.cache_manager,
plugin_manager=self.plugin_manager
)
def test_plugin_initialization(self):
self.assertEqual(self.plugin.plugin_id, "my-plugin")
self.assertTrue(self.plugin.enabled)
def test_config_validation(self):
self.assertTrue(self.plugin.validate_config())
def test_update(self):
self.cache_manager.get.return_value = None
self.plugin.update()
# Assert data was fetched and cached
def test_display(self):
self.plugin.display()
self.display_manager.draw_text.assert_called()
self.display_manager.update_display.assert_called()
if __name__ == '__main__':
unittest.main()
```
Run tests:
```bash
cd plugins/my-plugin
python -m pytest test_my_plugin.py
# or
python test_my_plugin.py
```
### Integration Testing
Test plugin with actual managers:
```python
# test_plugin_integration.py
from src.plugin_system.plugin_manager import PluginManager
from src.config_manager import ConfigManager
from src.display_manager import DisplayManager
from src.cache_manager import CacheManager
def test_plugin_loading():
config_manager = ConfigManager()
config = config_manager.load_config()
display_manager = DisplayManager(config)
cache_manager = CacheManager()
plugin_manager = PluginManager(
plugins_dir="plugins",
config_manager=config_manager,
display_manager=display_manager,
cache_manager=cache_manager
)
plugins = plugin_manager.discover_plugins()
assert "my-plugin" in plugins
assert plugin_manager.load_plugin("my-plugin")
plugin = plugin_manager.get_plugin("my-plugin")
assert plugin is not None
assert plugin.enabled
plugin.update()
plugin.display()
```
### Emulator Testing
Test plugin rendering visually:
```bash
# Run with emulator
python run.py --emulator
# Plugin should appear in display rotation
# Check logs for plugin loading and execution
```
### Hardware Testing
1. Deploy plugin to Raspberry Pi
2. Enable in `config/config.json`
3. Restart LEDMatrix service
4. Observe LED matrix display
5. Check logs: `journalctl -u ledmatrix -f`
---
## Troubleshooting
### Plugin Not Loading
**Symptoms**: Plugin doesn't appear in available modes, no logs about plugin
**Solutions**:
1. Check plugin directory exists: `ls plugins/my-plugin/`
2. Verify `manifest.json` exists and is valid JSON
3. Check manifest has required fields: `id`, `entry_point`, `class_name`
4. Verify entry_point file exists: `ls plugins/my-plugin/manager.py`
5. Check class name matches: `grep "class.*Plugin" plugins/my-plugin/manager.py`
6. Review logs for import errors
### Plugin Loading but Not Displaying
**Symptoms**: Plugin loads successfully but doesn't appear in rotation
**Solutions**:
1. Check plugin is enabled: `config/config.json` has `"enabled": true`
2. Verify display_modes in manifest match config
3. Check plugin is in rotation schedule
4. Review `display()` method for errors
5. Check logs for runtime errors
### Configuration Errors
**Symptoms**: Plugin fails to load, validation errors in logs
**Solutions**:
1. Validate config against `config_schema.json`
2. Check required fields are present
3. Verify data types match schema
4. Check for typos in config keys
5. Review `validate_config()` method
### Import Errors
**Symptoms**: ModuleNotFoundError or ImportError in logs
**Solutions**:
1. Install plugin dependencies: `pip install -r plugins/my-plugin/requirements.txt`
2. Check Python path includes plugin directory
3. Verify relative imports are correct
4. Check for circular import issues
5. Ensure all dependencies are in requirements.txt
### Display Issues
**Symptoms**: Plugin renders incorrectly or not at all
**Solutions**:
1. Check display dimensions: `display_manager.width`, `display_manager.height`
2. Verify coordinates are within display bounds
3. Check color values are valid (0-255)
4. Ensure `update_display()` is called after rendering
5. Test with emulator first to debug rendering
### Performance Issues
**Symptoms**: Slow display updates, high CPU usage
**Solutions**:
1. Use `cache_manager` to avoid excessive API calls
2. Implement background data fetching
3. Optimize rendering code
4. Consider using `high_performance_transitions`
5. Profile plugin code to identify bottlenecks
### Git/Symlink Issues
**Symptoms**: Plugin changes not appearing, broken symlinks
**Solutions**:
1. Check symlink: `ls -la plugins/my-plugin`
2. Verify target exists: `readlink -f plugins/my-plugin`
3. Update plugin: `./scripts/dev/dev_plugin_setup.sh update my-plugin`
4. Re-link plugin if needed: `./scripts/dev/dev_plugin_setup.sh unlink my-plugin && ./scripts/dev/dev_plugin_setup.sh link my-plugin <path>`
5. Check git status: `cd plugins/my-plugin && git status`
---
## Best Practices
### Code Organization
- Keep plugin code in `plugins/<plugin-id>/` directory
- Use descriptive class and method names
- Follow existing plugin patterns
- Place shared utilities in `src/common/` if reusable
### Configuration
- Always use `config_schema.json` for validation
- Store secrets in `config_secrets.json`
- Provide sensible defaults
- Document all configuration options in README
### Error Handling
- Use plugin logger for all logging
- Handle API failures gracefully
- Provide fallback displays when data unavailable
- Cache data to avoid excessive requests
### Performance
- Cache API responses appropriately
- Use background data fetching for long operations
- Optimize rendering for Pi's limited resources
- Test performance on actual hardware
### Testing
- Write unit tests for core logic
- Test with emulator before hardware
- Test on Raspberry Pi before deploying
- Test with other plugins enabled
### Documentation
- Document plugin functionality in README
- Include configuration examples
- Document API requirements and rate limits
- Provide usage examples
---
## Resources
- **Plugin System Documentation**: `docs/PLUGIN_ARCHITECTURE_SPEC.md`
- **Base Plugin Class**: `src/plugin_system/base_plugin.py`
- **Plugin Manager**: `src/plugin_system/plugin_manager.py`
- **Example Plugins**:
- `plugins/hockey-scoreboard/` - Sports scoreboard example
- `plugins/football-scoreboard/` - Complex multi-league example
- `plugins/ledmatrix-music/` - Real-time data example
- **Development Setup**: `dev_plugin_setup.sh`
- **Example Config**: `dev_plugins.json.example`
---
## Quick Reference
### Common Commands
```bash
# Link plugin from GitHub
./scripts/dev/dev_plugin_setup.sh link-github <name>
# Link local plugin
./scripts/dev/dev_plugin_setup.sh link <name> <path>
# List all plugins
./scripts/dev/dev_plugin_setup.sh list
# Check plugin status
./scripts/dev/dev_plugin_setup.sh status
# Update plugin(s)
./scripts/dev/dev_plugin_setup.sh update [name]
# Unlink plugin
./scripts/dev/dev_plugin_setup.sh unlink <name>
# Run with emulator
python run.py --emulator
# Run on Pi
python run.py
```
### Plugin File Structure
```
plugins/my-plugin/
├── manifest.json # Required: Plugin metadata
├── manager.py # Required: Plugin class
├── config_schema.json # Required: Config validation
├── requirements.txt # Optional: Dependencies
├── README.md # Optional: Documentation
└── ... # Plugin-specific files
```
### Required Manifest Fields
- `id`: Plugin identifier
- `entry_point`: Python file (usually "manager.py")
- `class_name`: Plugin class name
- `display_modes`: Array of mode names
-38
View File
@@ -1,38 +0,0 @@
---
globs: *.py
---
# Python Coding Standards
## Code Quality Principles
- **Simplicity First**: Prefer clear, readable code over clever optimizations
- **Explicit over Implicit**: Make intentions clear through naming and structure
- **Fail Fast**: Validate inputs and handle errors early
- **Documentation**: Use docstrings for classes and complex functions
## Naming Conventions
- **Classes**: PascalCase (e.g., `NHLRecentManager`)
- **Functions/Variables**: snake_case (e.g., `fetch_game_data`)
- **Constants**: UPPER_SNAKE_CASE (e.g., `ESPN_NHL_SCOREBOARD_URL`)
- **Private methods**: Leading underscore (e.g., `_fetch_data`)
## Error Handling
- **Logging**: Use structured logging with context (e.g., `[NHL Recent]`)
- **Exceptions**: Catch specific exceptions, not bare `except:`
- **User-friendly messages**: Explain what went wrong and potential solutions
- **Graceful degradation**: Continue operation when non-critical features fail
## Manager Pattern
All sports managers should follow this structure:
```python
class BaseManager:
def __init__(self, config, display_manager, cache_manager)
def update(self) # Fetch and process data
def display(self, force_clear=False) # Render to display
```
## Configuration Management
- **Type hints**: Use for function parameters and return values
- **Configuration validation**: Check required fields on initialization
- **Default values**: Provide sensible defaults in code, not config
- **Environment awareness**: Handle different deployment contexts
@@ -1,42 +0,0 @@
---
globs: config/*.json,src/*.py
---
# Configuration Management
## Configuration Structure
- **Main config**: [config/config.json](mdc:config/config.json) - Primary configuration
- **Secrets**: [config/config_secrets.json](mdc:config/config_secrets.json) - API keys and sensitive data
- **Templates**: [config/config.template.json](mdc:config/config.template.json) - Default values
## Configuration Principles
- **Validation**: Check required fields and data types on startup
- **Defaults**: Provide sensible defaults in code, not just config
- **Environment awareness**: Handle development vs production differences
- **Security**: Never commit secrets to version control
## Manager Configuration Pattern
```python
def __init__(self, config, display_manager, cache_manager):
self.mode_config = config.get("sport_scoreboard", {})
self.favorite_teams = self.mode_config.get("favorite_teams", [])
self.show_favorite_only = self.mode_config.get("show_favorite_teams_only", False)
```
## Required Configuration Sections
- **Display settings**: Update intervals, display durations
- **API settings**: Timeouts, retry logic, rate limiting
- **Background service**: Threading, caching, priority settings
- **Team preferences**: Favorite teams, filtering options
## Configuration Validation
- **Type checking**: Ensure numeric values are numbers, lists are lists
- **Range validation**: Check that intervals are reasonable
- **Dependency checking**: Verify required services are available
- **Fallback values**: Provide defaults when config is missing or invalid
## Best Practices
- **Documentation**: Comment complex configuration options
- **Examples**: Provide working examples in templates
- **Migration**: Handle configuration changes between versions
- **Testing**: Validate configuration in test environments
-50
View File
@@ -1,50 +0,0 @@
---
globs: src/*.py
---
# Error Handling and Logging
## Logging Standards
- **Structured prefixes**: Use consistent tags like `[NHL Recent]`, `[NFL Live]`
- **Context information**: Include relevant details (team names, game status, dates)
- **Appropriate levels**:
- `info`: Normal operations and status updates
- `debug`: Detailed information for troubleshooting
- `warning`: Non-critical issues that should be noted
- `error`: Problems that need attention
## Error Handling Patterns
```python
try:
data = self._fetch_data()
if not data or 'events' not in data:
self.logger.warning("[Manager] No events found in API response")
return
except requests.exceptions.RequestException as e:
self.logger.error(f"[Manager] API error: {e}")
return None
```
## User-Friendly Messages
- **Explain the situation**: "No games available during off-season"
- **Provide context**: "NHL season typically runs October-June"
- **Suggest solutions**: "Check back when season starts"
- **Distinguish issues**: API problems vs no data vs filtering results
## Graceful Degradation
- **Fallback content**: Show alternative games when favorites unavailable
- **Cached data**: Use cached data when API fails
- **Service continuity**: Continue operation when non-critical features fail
- **Clear communication**: Explain what's happening to users
## Debugging Support
- **Comprehensive logging**: Log API responses, filtering results, display updates
- **State tracking**: Log current state and transitions
- **Performance monitoring**: Track timing and resource usage
- **Error context**: Include stack traces for debugging
## Off-Season Awareness
- **Seasonal messaging**: Different messages for different times of year
- **Helpful context**: Explain why no games are available
- **Future planning**: Mention when season starts
- **Realistic expectations**: Set appropriate expectations during off-season
-51
View File
@@ -1,51 +0,0 @@
---
alwaysApply: true
---
# Git Workflow and Branching
## Branch Naming Conventions
- **Features**: `feature/description-of-feature` (e.g., `feature/weather-forecast-improvements`)
- **Bug fixes**: `fix/description-of-bug` (e.g., `fix/nhl-manager-improvements`)
- **Hotfixes**: `hotfix/critical-issue-description`
- **Refactoring**: `refactor/description-of-refactor`
## Commit Message Format
```
type(scope): description
[optional body]
[optional footer]
```
**Types**: feat, fix, docs, style, refactor, test, chore
**Examples**:
- `feat(nhl): Add enhanced logging for data visibility`
- `fix(display): Resolve rendering performance issue`
- `docs(api): Update ESPN API integration guide`
## Pull Request Guidelines
- **Self-review**: Review your own PR before requesting review
- **Testing**: Test thoroughly on Raspberry Pi hardware
- **Documentation**: Update relevant documentation if needed
- **Clean history**: Squash commits if necessary for clean history
## Code Review Checklist
- **Code Quality**: Proper error handling, logging, type hints
- **Architecture**: Follows project patterns, doesn't break existing functionality
- **Performance**: No negative impact on display performance
- **Testing**: Works on Raspberry Pi hardware
- **Documentation**: Comments added for complex logic
## Merge Strategies
- **Squash and Merge**: Preferred for feature branches and bug fixes
- **Merge Commit**: For complex features with multiple logical commits
- **Rebase and Merge**: For simple, single-commit changes
## Best Practices
- **Keep branches small and focused**
- **Commit frequently with meaningful messages**
- **Update branch regularly with main**
- **Test changes incrementally**
- **Delete feature branches after merge**
-213
View File
@@ -1,213 +0,0 @@
---
description: GitHub branching and pull request best practices for LEDMatrix project
globs: ["**/*.py", "**/*.md", "**/*.json", "**/*.sh"]
alwaysApply: true
---
# GitHub Branching and Pull Request Guidelines
## Branch Naming Conventions
### Feature Branches
- **Format**: `feature/description-of-feature`
- **Examples**:
- `feature/weather-forecast-improvements`
- `feature/stock-api-integration`
- `feature/nba-live-scores`
### Bug Fix Branches
- **Format**: `fix/description-of-bug`
- **Examples**:
- `fix/leaderboard-scrolling-performance`
- `fix/weather-api-timeout`
- `fix/display-rendering-issue`
### Hotfix Branches
- **Format**: `hotfix/critical-issue-description`
- **Examples**:
- `hotfix/display-crash-fix`
- `hotfix/api-rate-limit-fix`
### Refactoring Branches
- **Format**: `refactor/description-of-refactor`
- **Examples**:
- `refactor/sports-manager-architecture`
- `refactor/cache-management-system`
## Branch Management Rules
### Main Branch Protection
- **`main`** branch is protected and requires PR reviews
- Never commit directly to `main`
- All changes must go through pull requests
### Branch Lifecycle
1. **Create** branch from `main` when starting work
2. **Keep** branch up-to-date with `main` regularly
3. **Test** thoroughly before creating PR
4. **Delete** branch after successful merge
### Branch Updates
```bash
# Before starting new work
git checkout main
git pull origin main
# Create new branch
git checkout -b feature/your-feature-name
# Keep branch updated during development
git checkout main
git pull origin main
git checkout feature/your-feature-name
git merge main
```
## Pull Request Guidelines
### PR Title Format
- **Feature**: `feat: Add weather forecast improvements`
- **Fix**: `fix: Resolve leaderboard scrolling performance issue`
- **Refactor**: `refactor: Improve sports manager architecture`
- **Docs**: `docs: Update API integration guide`
- **Test**: `test: Add unit tests for weather manager`
### PR Description Template
```markdown
## Description
Brief description of changes and motivation.
## Type of Change
- [ ] Bug fix (non-breaking change)
- [ ] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Documentation update
- [ ] Performance improvement
- [ ] Refactoring
## Testing
- [ ] Tested on Raspberry Pi hardware
- [ ] Verified display rendering works correctly
- [ ] Checked API integration functionality
- [ ] Tested error handling scenarios
## Screenshots/Videos
(If applicable, add screenshots or videos of the changes)
## Checklist
- [ ] Code follows project style guidelines
- [ ] Self-review completed
- [ ] Comments added for complex logic
- [ ] No hardcoded values or API keys
- [ ] Error handling implemented
- [ ] Logging added where appropriate
```
### PR Review Requirements
#### For Reviewers
- **Code Quality**: Check for proper error handling, logging, and type hints
- **Architecture**: Ensure changes follow project patterns and don't break existing functionality
- **Performance**: Verify changes don't negatively impact display performance
- **Testing**: Confirm changes work on Raspberry Pi hardware
- **Documentation**: Check if documentation needs updates
#### For Authors
- **Self-Review**: Review your own PR before requesting review
- **Testing**: Test thoroughly on Pi hardware before submitting
- **Documentation**: Update relevant documentation if needed
- **Clean History**: Squash commits if necessary for clean history
## Commit Message Guidelines
### Format
```
type(scope): description
[optional body]
[optional footer]
```
### Types
- **feat**: New feature
- **fix**: Bug fix
- **docs**: Documentation changes
- **style**: Code style changes (formatting, etc.)
- **refactor**: Code refactoring
- **test**: Adding or updating tests
- **chore**: Maintenance tasks
### Examples
```
feat(weather): Add hourly forecast display
fix(nba): Resolve live score update issue
docs(api): Update ESPN API integration guide
refactor(sports): Improve base class architecture
```
## Merge Strategies
### Squash and Merge (Preferred)
- Use for feature branches and bug fixes
- Creates clean, linear history
- Combines all commits into single commit
### Merge Commit
- Use for complex features with multiple logical commits
- Preserves commit history
- Use when commit messages are meaningful
### Rebase and Merge
- Use sparingly for simple, single-commit changes
- Creates linear history without merge commits
## Release Management
### Version Tags
- Use semantic versioning: `v1.2.3`
- Tag releases on `main` branch
- Create release notes with technical details
### Release Branches
- **Format**: `release/v1.2.3`
- Use for release preparation
- Include version bumps and final testing
## Emergency Procedures
### Hotfix Process
1. Create `hotfix/` branch from `main`
2. Make minimal fix
3. Test thoroughly
4. Create PR with expedited review
5. Merge to `main` and tag release
6. Cherry-pick to other branches if needed
### Rollback Process
1. Identify last known good commit
2. Create revert PR if possible
3. Use `git revert` for clean rollback
4. Tag rollback release
5. Document issue and resolution
## Best Practices
### Before Creating PR
- [ ] Run all tests locally
- [ ] Test on Raspberry Pi hardware
- [ ] Check for linting errors
- [ ] Update documentation if needed
- [ ] Ensure commit messages are clear
### During Development
- [ ] Keep branches small and focused
- [ ] Commit frequently with meaningful messages
- [ ] Update branch regularly with main
- [ ] Test changes incrementally
### After PR Approval
- [ ] Delete feature branch after merge
- [ ] Update local main branch
- [ ] Verify changes work in production
- [ ] Update any related documentation
-23
View File
@@ -1,23 +0,0 @@
---
alwaysApply: true
---
# LEDMatrix Project Structure
## Core Architecture
- **Main entry point**: [run.py](mdc:run.py) - Primary application launcher
- **Configuration**: [config/config.json](mdc:config/config.json) - Main configuration file
- **Display management**: [src/display_controller.py](mdc:src/display_controller.py) - Core display logic
- **Web interface**: [web_interface_v2.py](mdc:web_interface_v2.py) - Modern web UI
## Source Code Organization
- **Managers**: [src/](mdc:src/) - All sports/weather/stock managers
- **Assets**: [assets/](mdc:assets/) - Logos, fonts, and static resources
- **Tests**: [test/](mdc:test/) - Unit and integration tests
- **Documentation**: [LEDMatrix.wiki/](mdc:LEDMatrix.wiki/) - Comprehensive guides
## Key Design Principles
- **Single Responsibility**: Each manager handles one sport/domain
- **Consistent Patterns**: All managers follow similar structure
- **Configuration-Driven**: Behavior controlled via [config/config.json](mdc:config/config.json)
- **Raspberry Pi Focus**: Optimized for Pi hardware, not Windows development
@@ -1,41 +0,0 @@
---
alwaysApply: true
---
# Raspberry Pi Development Guidelines
## Hardware Constraints
- **Pi-only execution**: Code must run on Raspberry Pi, not Windows development machine
- **LED matrix library**: Uses [rpi-rgb-led-matrix-master/](mdc:rpi-rgb-led-matrix-master/) for hardware control
- **Memory limitations**: Optimize for Pi's limited RAM
- **Performance**: Consider Pi's CPU capabilities in design
## Development Workflow
- **Local development**: Write and test code on Windows
- **Pi deployment**: Deploy and test on actual Pi hardware
- **SSH access**: Use SSH for Pi-based testing and debugging
- **Service management**: Use systemd services for production deployment
## Testing Strategy
- **Unit tests**: Test logic without hardware dependencies
- **Integration tests**: Test with mock display managers
- **Hardware tests**: Validate on actual Pi with LED matrix
- **Performance tests**: Monitor memory and CPU usage
## Deployment Considerations
- **Service files**: [ledmatrix.service](mdc:ledmatrix.service), [ledmatrix-web.service](mdc:ledmatrix-web.service)
- **Installation scripts**: [first_time_install.sh](mdc:first_time_install.sh), [install_service.sh](mdc:install_service.sh)
- **Dependencies**: [requirements.txt](mdc:requirements.txt) for Pi environment
- **Permissions**: Handle file permissions for Pi user
## Performance Optimization
- **Caching**: Use [src/cache_manager.py](mdc:src/cache_manager.py) for data persistence
- **Background services**: Non-blocking data fetching
- **Memory management**: Clean up resources regularly
- **Display optimization**: Minimize unnecessary redraws
## Debugging on Pi
- **Logging**: Comprehensive logging for remote debugging
- **Error reporting**: Clear error messages for troubleshooting
- **Status monitoring**: Health checks and status reporting
- **Remote access**: Web interface for configuration and monitoring
-42
View File
@@ -1,42 +0,0 @@
---
globs: src/*_managers.py
---
# Sports Manager Development
## Manager Architecture
All sports managers inherit from base classes and follow consistent patterns:
- **Base classes**: [src/nhl_managers.py](mdc:src/nhl_managers.py), [src/nfl_managers.py](mdc:src/nfl_managers.py)
- **Common functionality**: Data fetching, caching, display rendering
- **Configuration-driven**: Behavior controlled via config sections
## Required Methods
```python
def __init__(self, config, display_manager, cache_manager)
def update(self) # Fetch fresh data
def display(self, force_clear=False) # Render current data
```
## Data Flow Pattern
1. **Fetch**: Get data from API (with caching)
2. **Process**: Extract relevant game information
3. **Filter**: Apply favorite team preferences
4. **Display**: Render to LED matrix
## Logging Standards
- **Structured prefixes**: `[NHL Recent]`, `[NFL Live]`, etc.
- **Context information**: Include team names, game status, dates
- **Debug levels**: Use appropriate log levels (info, debug, warning, error)
- **User-friendly messages**: Explain what's happening and why
## Error Handling
- **API failures**: Log and continue with cached data if available
- **No data scenarios**: Distinguish between API issues vs no games available
- **Off-season awareness**: Provide helpful context during non-active periods
- **Fallback behavior**: Show alternative content when preferred content unavailable
## Configuration Integration
- **Required settings**: Validate on initialization
- **Optional settings**: Provide sensible defaults
- **Background service**: Use for non-blocking data fetching
- **Caching strategy**: Implement intelligent cache management
-51
View File
@@ -1,51 +0,0 @@
---
globs: test/*.py,src/*.py
---
# Testing Standards
## Test Organization
- **Test directory**: [test/](mdc:test/) - All test files
- **Unit tests**: Test individual components in isolation
- **Integration tests**: Test component interactions
- **Hardware tests**: Validate on Raspberry Pi with actual LED matrix
## Testing Principles
- **Test behavior, not implementation**: Focus on what the code does, not how
- **Mock external dependencies**: Use mocks for APIs, display managers, cache
- **Test edge cases**: Empty data, API failures, configuration errors
- **Pi-specific testing**: Validate hardware integration
## Test Structure
```python
def test_manager_initialization():
"""Test that manager initializes with valid config"""
config = {"sport_scoreboard": {"enabled": True}}
manager = ManagerClass(config, mock_display, mock_cache)
assert manager.enabled == True
def test_api_failure_handling():
"""Test graceful handling of API failures"""
# Test that system continues when API fails
# Verify fallback to cached data
# Check appropriate error logging
```
## Mock Patterns
- **Display Manager**: Mock for testing without hardware
- **Cache Manager**: Mock for testing data persistence
- **API responses**: Mock for consistent test data
- **Configuration**: Use test-specific configs
## Test Categories
- **Unit tests**: Individual manager methods
- **Integration tests**: Manager interactions with services
- **Configuration tests**: Validate config loading and validation
- **Error handling tests**: API failures, invalid data, edge cases
## Testing Best Practices
- **Descriptive names**: Test names should explain what they test
- **Single responsibility**: Each test should verify one thing
- **Independent tests**: Tests should not depend on each other
- **Clean setup/teardown**: Reset state between tests
- **Pi compatibility**: Ensure tests work in Pi environment
-1
View File
@@ -1 +0,0 @@
# Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv)
-364
View File
@@ -1,364 +0,0 @@
# LEDMatrix Plugin Development Rules
## Plugin System Overview
The LEDMatrix project uses a plugin-based architecture. All display
functionality 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:167`).
> **Fallback note (scoped):** `PluginManager.discover_plugins()`
> (`src/plugin_system/plugin_manager.py:208`) only scans the
> configured directory — there is no fallback to `plugins/` in the
> main discovery path. A fallback to `plugins/` does exist in two
> narrower places:
> - `store_manager.py:2342-2373` — store operations (install/update/
> uninstall) check `plugins/` if the plugin isn't found in the
> configured directory, so plugin-store flows work even when your
> dev symlinks live in `plugins/`.
> - `schema_manager.py:48-80` — `get_schema_path()` probes both
> `plugins/` and `plugin-repos/` for `config_schema.json` so the
> web UI form generation finds the schema regardless of where the
> plugin lives.
>
> The dev workflow in `scripts/dev/dev_plugin_setup.sh` creates
> symlinks under `plugins/`, which is why the store and schema
> fallbacks exist. For day-to-day development, set
> `plugin_system.plugins_directory` to `plugins` so the main
> discovery path picks up your 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:
`python3 run.py --emulator` (or equivalently
`EMULATOR=true python3 run.py`, or `./scripts/dev/run_emulator.sh`).
The `-e`/`--emulator` CLI flag is defined in `run.py:19-20`.
- 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
- `delete(key)` / `clear_cache(key=None)`: Remove a single cache entry,
or (for `clear_cache` with no argument) every cached entry. `delete`
is an alias for `clear_cache(key)`.
- `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 the configured `plugin_system.plugins_directory` (default `plugin-repos/`) 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
```
plugin-repos/ # default install dir (plugins/ when using dev symlinks)
<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`
+13
View File
@@ -24,6 +24,16 @@
- Plugin instantiation args: `plugin_id, config, display_manager, cache_manager, plugin_manager`
- Config schemas use JSON Schema Draft-7
- Display dimensions: always read dynamically from `self.display_manager.matrix.width/height`
- Secrets: namespaced by plugin id in `config/config_secrets.json`, declared
via `"x-secret": true` in the plugin's config schema, and deep-merged into
the plugin's config dict at load time — plugins read them with plain
`config.get(...)`, never a separate accessor
## Dev Workflow
- Link a plugin for development: `./scripts/dev/dev_plugin_setup.sh link-github <name>` (or `link <name> <path>`); symlinks land in `plugins/` — set `plugin_system.plugins_directory` to `plugins` so discovery picks them up
- Browser preview without the display loop: `python3 scripts/dev_server.py` → http://localhost:5001
- Full display in emulator mode: `python3 run.py -e` (or `EMULATOR=true python3 run.py`)
- Validate one plugin headlessly: `python3 scripts/check_plugin.py --plugin <id>`
## Plugin Store Architecture
- Official plugins live in the `ledmatrix-plugins` monorepo (not individual repos)
@@ -46,4 +56,7 @@
## Common Pitfalls
- paho-mqtt 2.x needs `callback_api_version=mqtt.CallbackAPIVersion.VERSION1` for v1 compat
- BasePlugin uses `get_logger()` from `src.logging_config`, not standard `logging.getLogger()`
- `DisplayManager` has no `draw_image()` — paste onto the PIL image directly:
`self.display_manager.image.paste(img, (x, y))` then `update_display()`
(use a mask for transparency: `image.paste(rgba, (x, y), rgba)`)
- When modifying a plugin in the monorepo, you MUST bump `version` in its `manifest.json` and run `python update_registry.py` — otherwise users won't receive the update
+17 -4
View File
@@ -589,11 +589,24 @@ Your plugin must:
### 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
- **GitHub as source of truth**: the plugin store resolves versions in this
order: GitHub Releases → GitHub Tags → manifest from branch → git commit hash
- **Automatic version bumping**: install the self-contained pre-push hook in
your plugin repo and patch versions bump themselves on push (a git tag
`v{version}` is created and `manifest.json` staged automatically):
See the [Git Workflow rules](../.cursorrules) for version management details.
```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
```
Set `SKIP_TAG=1` in the environment to skip auto-tagging for one push.
- **Manual versioning**: only needed for major/minor bumps, CI pipelines that
bypass hooks, or forks without the hook — use
`scripts/bump_plugin_version.py`.
- **Registry stores no versions**: `plugins.json` holds only metadata (name,
description, repo URL).
### Submitting to Official Registry