From cc1db666629fab5e24b82910416d1a8b4f2eeb3d Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 6 Aug 2026 01:50:35 +0000 Subject: [PATCH] chore: remove Cursor IDE tooling, consolidate its guidance into CLAUDE.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Claude-Session: https://claude.ai/code/session_01SXb4mKcAkVaxkeTb3YnAdr --- .cursor/README.md | 145 ---- .cursor/plugin_templates/QUICK_START.md | 247 ------ .cursor/plugin_templates/README.md.template | 156 ---- .../config_schema.json.template | 44 - .cursor/plugin_templates/manager.py.template | 226 ------ .../plugin_templates/manifest.json.template | 55 -- .../requirements.txt.template | 13 - .../plugin_templates/test_manager.py.template | 136 ---- .cursor/plugins_guide.md | 751 ------------------ .cursor/rules/coding-standards.mdc | 38 - .cursor/rules/configuration-management.mdc | 42 - .cursor/rules/error-handling-logging.mdc | 50 -- .cursor/rules/git-workflow.mdc | 51 -- .cursor/rules/github-branches-rule.mdc | 213 ----- .cursor/rules/project-structure.mdc | 23 - .cursor/rules/raspberry-pi-development.mdc | 41 - .cursor/rules/sports-managers.mdc | 42 - .cursor/rules/testing-standards.mdc | 51 -- .cursorignore | 1 - .cursorrules | 364 --------- CLAUDE.md | 13 + docs/PLUGIN_DEVELOPMENT_GUIDE.md | 21 +- .../CURSOR_PLUGIN_SCHEMA_AUDIT_PLAN.md | 0 23 files changed, 30 insertions(+), 2693 deletions(-) delete mode 100644 .cursor/README.md delete mode 100644 .cursor/plugin_templates/QUICK_START.md delete mode 100644 .cursor/plugin_templates/README.md.template delete mode 100644 .cursor/plugin_templates/config_schema.json.template delete mode 100644 .cursor/plugin_templates/manager.py.template delete mode 100644 .cursor/plugin_templates/manifest.json.template delete mode 100644 .cursor/plugin_templates/requirements.txt.template delete mode 100644 .cursor/plugin_templates/test_manager.py.template delete mode 100644 .cursor/plugins_guide.md delete mode 100644 .cursor/rules/coding-standards.mdc delete mode 100644 .cursor/rules/configuration-management.mdc delete mode 100644 .cursor/rules/error-handling-logging.mdc delete mode 100644 .cursor/rules/git-workflow.mdc delete mode 100644 .cursor/rules/github-branches-rule.mdc delete mode 100644 .cursor/rules/project-structure.mdc delete mode 100644 .cursor/rules/raspberry-pi-development.mdc delete mode 100644 .cursor/rules/sports-managers.mdc delete mode 100644 .cursor/rules/testing-standards.mdc delete mode 100644 .cursorignore delete mode 100644 .cursorrules rename .cursor/plans/implement_audit_fixes_plan.md => docs/archive/CURSOR_PLUGIN_SCHEMA_AUDIT_PLAN.md (100%) diff --git a/.cursor/README.md b/.cursor/README.md deleted file mode 100644 index 0b6d621f..00000000 --- a/.cursor/README.md +++ /dev/null @@ -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 -``` - -## 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/` - diff --git a/.cursor/plugin_templates/QUICK_START.md b/.cursor/plugin_templates/QUICK_START.md deleted file mode 100644 index 9a9f9591..00000000 --- a/.cursor/plugin_templates/QUICK_START.md +++ /dev/null @@ -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 - diff --git a/.cursor/plugin_templates/README.md.template b/.cursor/plugin_templates/README.md.template deleted file mode 100644 index ba26fd1b..00000000 --- a/.cursor/plugin_templates/README.md.template +++ /dev/null @@ -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 - diff --git a/.cursor/plugin_templates/config_schema.json.template b/.cursor/plugin_templates/config_schema.json.template deleted file mode 100644 index 8d512038..00000000 --- a/.cursor/plugin_templates/config_schema.json.template +++ /dev/null @@ -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 -} - diff --git a/.cursor/plugin_templates/manager.py.template b/.cursor/plugin_templates/manager.py.template deleted file mode 100644 index 6afe3e9b..00000000 --- a/.cursor/plugin_templates/manager.py.template +++ /dev/null @@ -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() - diff --git a/.cursor/plugin_templates/manifest.json.template b/.cursor/plugin_templates/manifest.json.template deleted file mode 100644 index ade5fa58..00000000 --- a/.cursor/plugin_templates/manifest.json.template +++ /dev/null @@ -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": "" -} - diff --git a/.cursor/plugin_templates/requirements.txt.template b/.cursor/plugin_templates/requirements.txt.template deleted file mode 100644 index a631c4f0..00000000 --- a/.cursor/plugin_templates/requirements.txt.template +++ /dev/null @@ -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 - diff --git a/.cursor/plugin_templates/test_manager.py.template b/.cursor/plugin_templates/test_manager.py.template deleted file mode 100644 index 0cff3425..00000000 --- a/.cursor/plugin_templates/test_manager.py.template +++ /dev/null @@ -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() - diff --git a/.cursor/plugins_guide.md b/.cursor/plugins_guide.md deleted file mode 100644 index 62ba63b6..00000000 --- a/.cursor/plugins_guide.md +++ /dev/null @@ -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 - -# 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 https://github.com/user/repo.git -``` - -The script will: -- Clone the repository to `~/.ledmatrix-dev-plugins/` (or configured directory) -- Create a symlink in `plugins//` 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 - -# 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 -{ - "": { - "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 ` -5. Check git status: `cd plugins/my-plugin && git status` - ---- - -## Best Practices - -### Code Organization - -- Keep plugin code in `plugins//` 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 - -# Link local plugin -./scripts/dev/dev_plugin_setup.sh link - -# 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 - -# 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 - diff --git a/.cursor/rules/coding-standards.mdc b/.cursor/rules/coding-standards.mdc deleted file mode 100644 index 5abeb94e..00000000 --- a/.cursor/rules/coding-standards.mdc +++ /dev/null @@ -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 \ No newline at end of file diff --git a/.cursor/rules/configuration-management.mdc b/.cursor/rules/configuration-management.mdc deleted file mode 100644 index 37f68300..00000000 --- a/.cursor/rules/configuration-management.mdc +++ /dev/null @@ -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 \ No newline at end of file diff --git a/.cursor/rules/error-handling-logging.mdc b/.cursor/rules/error-handling-logging.mdc deleted file mode 100644 index 0bf404ee..00000000 --- a/.cursor/rules/error-handling-logging.mdc +++ /dev/null @@ -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 \ No newline at end of file diff --git a/.cursor/rules/git-workflow.mdc b/.cursor/rules/git-workflow.mdc deleted file mode 100644 index c28c603a..00000000 --- a/.cursor/rules/git-workflow.mdc +++ /dev/null @@ -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** \ No newline at end of file diff --git a/.cursor/rules/github-branches-rule.mdc b/.cursor/rules/github-branches-rule.mdc deleted file mode 100644 index 87b1ea5f..00000000 --- a/.cursor/rules/github-branches-rule.mdc +++ /dev/null @@ -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 diff --git a/.cursor/rules/project-structure.mdc b/.cursor/rules/project-structure.mdc deleted file mode 100644 index c9878a27..00000000 --- a/.cursor/rules/project-structure.mdc +++ /dev/null @@ -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 \ No newline at end of file diff --git a/.cursor/rules/raspberry-pi-development.mdc b/.cursor/rules/raspberry-pi-development.mdc deleted file mode 100644 index 829b7591..00000000 --- a/.cursor/rules/raspberry-pi-development.mdc +++ /dev/null @@ -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 \ No newline at end of file diff --git a/.cursor/rules/sports-managers.mdc b/.cursor/rules/sports-managers.mdc deleted file mode 100644 index fe6c6fc8..00000000 --- a/.cursor/rules/sports-managers.mdc +++ /dev/null @@ -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 \ No newline at end of file diff --git a/.cursor/rules/testing-standards.mdc b/.cursor/rules/testing-standards.mdc deleted file mode 100644 index 274b76ef..00000000 --- a/.cursor/rules/testing-standards.mdc +++ /dev/null @@ -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 \ No newline at end of file diff --git a/.cursorignore b/.cursorignore deleted file mode 100644 index 6f9f00ff..00000000 --- a/.cursorignore +++ /dev/null @@ -1 +0,0 @@ -# Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv) diff --git a/.cursorrules b/.cursorrules deleted file mode 100644 index 9b8ec283..00000000 --- a/.cursorrules +++ /dev/null @@ -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 - -# Link local repository -./scripts/dev/dev_plugin_setup.sh link -``` - -**Option B: Manual Setup** -1. Create directory in `plugin-repos//` (or `plugins//` - 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 `"": {"enabled": true}` - -### 2. Plugin Configuration - -Plugins are configured in `config/config.json`: -```json -{ - "": { - "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//` (or its dev-time - symlink in `plugins//`) -- 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) - / - 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-` - -**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` - diff --git a/CLAUDE.md b/CLAUDE.md index 01f4aa12..b7e027c4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 ` (or `link `); 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 ` ## 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 diff --git a/docs/PLUGIN_DEVELOPMENT_GUIDE.md b/docs/PLUGIN_DEVELOPMENT_GUIDE.md index f587629c..e2a0c240 100644 --- a/docs/PLUGIN_DEVELOPMENT_GUIDE.md +++ b/docs/PLUGIN_DEVELOPMENT_GUIDE.md @@ -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 diff --git a/.cursor/plans/implement_audit_fixes_plan.md b/docs/archive/CURSOR_PLUGIN_SCHEMA_AUDIT_PLAN.md similarity index 100% rename from .cursor/plans/implement_audit_fixes_plan.md rename to docs/archive/CURSOR_PLUGIN_SCHEMA_AUDIT_PLAN.md