Files
LEDMatrix/.cursor/plugin_templates/QUICK_START.md
Chuck 93e2d29af6 docs: fix .cursor/ helper docs
The .cursor/ directory holds the dev-side helper docs that Cursor and
contributors using AI tooling rely on to bootstrap plugin development.
Several of them had the same bug patterns as the user-facing docs.

.cursor/plugin_templates/QUICK_START.md
- "Adding Image Rendering" section showed
  display_manager.draw_image(image, x=0, y=0). That method doesn't
  exist on DisplayManager (same bug as PLUGIN_API_REFERENCE.md and
  PLUGIN_DEVELOPMENT_GUIDE.md). Replaced with the canonical
  display_manager.image.paste((x,y)) pattern, including the
  transparency-mask form.

.cursor/plugins_guide.md
- 10 occurrences of ./dev_plugin_setup.sh — the script lives at
  scripts/dev/dev_plugin_setup.sh, so anyone copy-pasting these
  examples gets "command not found". Bulk fixed via sed.
- "Test with emulator: python run.py --emulator" — there's no
  --emulator flag. Replaced with the real options:
  EMULATOR=true python3 run.py for the full display, or
  scripts/dev_server.py for the dev preview.
- Secrets management section showed a fictional
  "config_secrets": { "api_key": "my-plugin.api_key" } reference
  field. Verified in src/config_manager.py:162-172 that secrets are
  loaded by deep-merging config_secrets.json into the main config.
  There is no separate reference field — just put the secret under
  the same plugin namespace and read it from the merged config.
  Rewrote the section with the real pattern.
- "ssh pi@raspberrypi" -> "ssh ledpi@your-pi-ip" (consistent with
  the rest of LEDMatrix docs which use ledpi as the default user)

.cursor/README.md
- Same ./dev_plugin_setup.sh -> ./scripts/dev/dev_plugin_setup.sh
  fix (×6 occurrences via replace_all).
- Same "python run.py --emulator" -> "EMULATOR=true python3 run.py"
  fix. Also added a pointer to scripts/dev_server.py for previewing
  plugins without running the full display.
- "Example Plugins: plugins/hockey-scoreboard/" — the canonical
  source is the ledmatrix-plugins repo. Installed copies land in
  plugin-repos/ or plugins/. Updated the line to point at the
  ledmatrix-plugins repo and explain both local locations.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 10:08:46 -04:00

5.3 KiB

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

cd /path/to/LEDMatrix
mkdir -p plugins/my-plugin
cd plugins/my-plugin

Step 2: Copy Templates

# 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_IDmy-plugin (lowercase, use hyphens)
  • Plugin Name → Your plugin's display name
  • PluginClassNameMyPlugin (PascalCase)
  • Update description, author, homepage, etc.

manager.py

Replace placeholders:

  • PluginClassNameMyPlugin (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_IDmy-plugin
  • Plugin Name → Your plugin's name
  • Fill in features, installation, configuration sections

requirements.txt

Add your plugin's dependencies:

requests>=2.28.0
pillow>=9.0.0

Step 4: Enable Plugin

Edit config/config.json:

{
  "my-plugin": {
    "enabled": true,
    "display_duration": 15
  }
}

Step 5: Test Plugin

Test with Emulator

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:

# 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:
{
  "api_key": {
    "type": "string",
    "description": "API key for service"
  }
}
  1. Implement API call in _fetch_data():
import requests

def _fetch_data(self):
    response = requests.get(
        "https://api.example.com/data",
        headers={"Authorization": f"Bearer {self.api_key}"}
    )
    return response.json()
  1. Store API key in config/config_secrets.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():

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:

icon = Image.open("assets/icon.png").convert("RGBA")
self.display_manager.image.paste(icon, (5, 5), icon)

Adding Live Priority

  1. Enable in config:
{
  "my-plugin": {
    "live_priority": true
  }
}
  1. Implement has_live_content():
def has_live_content(self) -> bool:
    return self.data and self.data.get("is_live", False)
  1. Override get_live_modes() if needed:
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