Files
LEDMatrix/docs/PLUGIN_CONFIGURATION_GUIDE.md
T
ChuckandClaude Opus 5.5 a231d4dbc7 chore: delete unreferenced scripts and archived docs; fix stale doc claims (#607)
* chore(scripts): delete unreferenced helper scripts

None of these is referenced by an installer, systemd unit, CI workflow,
test, the web UI or src/:

- utils/cleanup_venv.sh removes venv_web_v2, which nothing creates
- utils/clear_python_cache.sh hardcodes ~/LEDMatrix and a .webassets-cache
  nothing uses
- install/migrate_config.sh only copies the template, which the installer
  and ConfigManager already do
- install/debug_install.sh, debug/debug_web_manual.py
- diagnose_web_ui.sh and verify_web_ui.sh overlap diagnose_web_interface.sh,
  which the docs point to
- fix_internet_connectivity.sh is iptables-only (stale on nftables)
- diagnose_plugin_permissions.sh, dev/validate_python.py
- download_nba_logos.py + README_NBA_LOGOS.md: logo_downloader fetches
  logos on demand
- setup_plugin_repos.py linked into the production plugin-repos/ dir; the
  dev workflow is scripts/dev/dev_plugin_setup.sh, and
  MULTI_ROOT_WORKSPACE_SETUP.md now uses it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(config): drop unused plugin_system flags and a dead unit comment

- config.template.json: remove plugin_system.auto_discover,
  auto_load_enabled and development_mode. Nothing reads them; the web UI
  only stores them when a client sends them. ConfigManager's migration
  only adds template keys, so existing configs keep theirs unchanged.
- config.template.json: re-indent vegas_scroll's live_* keys.
- systemd/ledmatrix.service: remove the comment documenting
  LEDMATRIX_ON_DEMAND_PLUGIN / on_demand_env.conf; nothing reads either.
- CONFIG_REFERENCE.md: say the legacy keys are no longer in the template.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: delete docs/archive and PLUGIN_IMPLEMENTATION_SUMMARY.md

- docs/archive/: superseded guides; the repository history keeps them
  and no live doc links into the directory. The one open document in it,
  WEB_UI_AUDIT_2026-09.md, moves to docs/audits/ and is linked from the
  docs index.
- PLUGIN_IMPLEMENTATION_SUMMARY.md invented usage statistics, called
  v2.0.0 current, listed shipped auto-updates as future work and
  documented a BasePlugin.get_config() that does not exist.
- docs/README.md: drop both, and stop telling contributors to archive
  obsolete pages instead of deleting them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(plugin-api): fix extra_small_font size, cache metric key and scroll pacing example

- PLUGIN_API_REFERENCE: extra_small_font loads at 7, not 6 (crisp_size
  snaps it, src/display_manager.py); get_cache_metrics() returns
  cache_hit_rate, not hit_rate (src/cache/cache_metrics.py).
- ADVANCED_PLUGIN_DEVELOPMENT: the basic scrolling example slept in a loop
  and never passed frame_hold; use ScrollHelper + scroll_config.configure()
  and set_scrolling_state(True, frame_hold=...) as PLUGIN_API_REFERENCE does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(plugin-config): match the config tab, icon and web-action docs to the code

- PLUGIN_CONFIG_QUICK_START / PLUGIN_CONFIGURATION_TABS /
  PLUGIN_CONFIGURATION_GUIDE: there is no "Reset to Defaults" button (the
  tab has Refresh, Update, Uninstall, Save Configuration); plugin config
  hot-reloads (ConfigService + on_config_change), so no restart; the
  schema is found by the fixed name config_schema.json, not a manifest
  config_schema field; the tab row is "Plugin Manager", not "Plugins";
  forms are server-rendered from /v3/partials/plugin-config/<id>; the
  duration hook is get_display_duration()/display_duration; a class_name
  mismatch raises PluginError; the store requires id, name, class_name and
  display_modes (not version); plugin_system.debug/log_level do not exist
  (use run.py -d / LEDMATRIX_DEBUG). Drop "future" features that shipped.
- PLUGIN_CONFIG_CORE_PROPERTIES: list all of CORE_PLUGIN_PROPERTIES,
  including skin, skin_options and the vegas_* tuning keys.
- PLUGIN_CUSTOM_ICONS: icon is only a Font Awesome class (fallback
  fa-puzzle-piece); emoji/URL icons and getPluginIcon() never existed in
  v3. Note that /api/v3/plugins/installed currently omits icon.
- PLUGIN_WEB_UI_ACTIONS (+ example JSON): success_message, error_message
  and step1_message are never read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(store): describe the monorepo registry and the store UI as they are

- PLUGIN_STORE_GUIDE: the Plugin Store is a section of the Plugin
  Manager tab; URL installs are "Install from GitHub" -> "Install Single
  Plugin"; bulk update exists (Check & Update All) plus opt-in weekly
  auto-update; PluginStoreManager() defaults to plugins/, so the Python
  examples pass plugin-repos; registry plugins are downloaded (GitHub API,
  ZIP fallback), not cloned; updates compare version with latest_version.
- PLUGIN_REGISTRY_SETUP_GUIDE: replace the per-plugin-repo + tag
  walkthrough with a short page on the monorepo registry (plugin_path,
  latest_version, update_registry.py) that points at the monorepo's own
  SUBMISSION.md. Drops the reference to the deleted
  PLUGIN_IMPLEMENTATION_SUMMARY.md and setup_plugin_repos.py.
- plugin_registry_template.json: use the real entry shape.
- PLUGIN_QUICK_REFERENCE: automatic background updates exist (opt-in);
  registry example and publishing steps use the monorepo, not tags.
- PLUGIN_DEVELOPMENT_GUIDE: tags/releases are not read by the store.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(readme): fix the Triple Bonnet mapping, install prerequisites and backup names

- README: the Adafruit Triple Bonnet uses `regular` (3 outputs), not
  `regular-pi1` (1 output) -- src/matrix_support.py MAPPING_OUTPUTS, and
  the README's own hardware_mapping section; the template default mapping
  is adafruit-hat, the PWM mod switches it to adafruit-hat-pwm; manual
  install only needs git up front (first_time_install.sh installs
  python-dev-is-python3, cmake, ninja-build etc.; cython3/scons are not
  used); the Pi Zero 2 W is a supported low-memory board, consistent with
  PRODUCT.md, LOW_MEMORY_BOARDS.md and the installer's low-memory build;
  fix the "First_time_install.sh" spelling, an orphan "2." list item and
  the hello-world starter link (it lives in the plugins monorepo).
- CONFIG_DEBUGGING: automatic backups are
  config/backups/config.json.backup.<YYYYMMDD_HHMMSS_ffffff> (five kept),
  not config_YYYYMMDD_HHMMSS.json.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(dev): correct the test-running and rgbmatrix build instructions

- HOW_TO_RUN_TESTS: coverage is not collected by a plain pytest run and
  pytest.ini has no threshold; the only one is --cov-fail-under=52 in the
  core unit-test job of .github/workflows/test.yml, which runs the whole
  test/ tree (not an allowlist). Almost no tests carry markers, so
  -m integration / -m slow select nothing; drop them and -m unit as the
  quick check. Replace the hardcoded /home/chuck path.
- DEVELOPMENT: the rgbmatrix package is built with pip install . from
  the submodule root (scikit-build-core + CMake + Ninja), as
  first_time_install.sh does; there is no make build-python /
  bindings/python step, and the build deps are python-dev-is-python3,
  cmake and ninja-build, not cython3/scons.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(wifi): the setup AP is open; auto-enable can be turned off without code changes

- WIFI_NETWORK_SETUP / SSH_UNAVAILABLE_AFTER_INSTALL: both AP paths in
  src/wifi_manager.py create an open network and nothing reads
  ap_password, so drop the "ledmatrix123" password and the ap_password
  key/advice.
- SSH_UNAVAILABLE_AFTER_INSTALL: disabling automatic AP mode does not
  need code changes -- auto_enable_ap_mode is a WiFi-tab toggle and
  POST /api/v3/wifi/ap/auto-enable; note the monitor daemon reads
  wifi_config.json at start, so restart it after changing the setting.
  Use the ledpi username and a relative install path like the other docs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(reference): add auto_update, drop drifted line numbers, fix UI and service details

- CONFIG_REFERENCE: document the top-level auto_update.enabled key (read
  by web_interface/auto_update.py and src/auto_update_setup.py); replace
  drifted file:line references with function names; the template's
  dim_schedule mode is "global".
- ADVANCED_FEATURES: core does not read a per-plugin background_service
  block (the sports plugins read their own), and priority is "higher
  number = higher priority" on FetchRequest but not used for ordering.
- WEB_INTERFACE_GUIDE: the General tab toggle is "Web Display Autostart"
  (web interface service), brightness is 1-100, and config paths are
  relative to the LEDMatrix folder, not /config.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: drop references to code removed in #608

get_installed_plugin_info, WiFiManager's saved_networks and the six
always-skipping plugin test files are deleted there. NetworkManager already
remembers joined networks; LEDMatrix no longer stores WiFi passwords.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: don't link SKIN_SYSTEM.md from the core-properties page

#615 deletes SKIN_SYSTEM.md; with this link, whichever of the two merged
second would break test_doc_links. The skin/skin_options entries go when
#615 removes the keys.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 12:36:38 -04:00

11 KiB

Plugin Configuration Guide

Overview

The LEDMatrix system uses a plugin-based architecture where each plugin manages its own configuration. This guide explains the configuration structure, how to configure plugins via the web interface, and advanced configuration options.

Quick Start

  1. Install a plugin from the Plugin Store in the web interface
  2. Navigate to the plugin's configuration tab (automatically created when installed)
  3. Configure settings using the auto-generated form
  4. Save configuration; the running display applies it without a restart

For detailed information, see the sections below.

Configuration Structure

Core System Configuration

The main configuration file (config/config.json) now contains only essential system settings:

{
    "web_display_autostart": true,
    "schedule": {
        "enabled": true,
        "start_time": "07:00",
        "end_time": "23:00"
    },
    "timezone": "America/Chicago",
    "location": {
        "city": "Dallas",
        "state": "Texas",
        "country": "US"
    },
    "display": {
        "hardware": {
            "rows": 32,
            "cols": 64,
            "chain_length": 2,
            "parallel": 1,
            "brightness": 90,
            "hardware_mapping": "adafruit-hat",
            "scan_mode": 0,
            "pwm_bits": 9,
            "pwm_dither_bits": 1,
            "pwm_lsb_nanoseconds": 130,
            "disable_hardware_pulsing": false,
            "inverse_colors": false,
            "show_refresh_rate": false,
            "limit_refresh_rate_hz": 100
        },
        "runtime": {
            "gpio_slowdown": 3
        },
        "display_durations": {
            "calendar": 30
        },
        "use_short_date_format": true
    },
    "calendar": {
        "enabled": false,
        "update_interval": 3600,
        "max_events": 5,
        "show_all_day": true,
        "date_format": "%m/%d",
        "time_format": "%I:%M %p"
    },
    "plugin_system": {
        "plugins_directory": "plugin-repos"
    }
}

Configuration Sections

1. System Settings

  • web_display_autostart: Enable web interface auto-start
  • schedule: Display schedule settings
  • timezone: System timezone
  • location: Default location for location-based plugins

2. Display Hardware

  • hardware: LED matrix hardware configuration
  • runtime: Runtime display settings
  • display_durations: How long each display mode shows (in seconds)
  • use_short_date_format: Use short date format

3. Core Components

  • calendar: Calendar manager settings (core system component)

4. Plugin System

  • plugin_system: Plugin system configuration
    • plugins_directory: Directory where plugins are stored (the only one the loader scans)
    • auto_discover, auto_load_enabled, development_mode may still appear in older configs; nothing reads them (see CONFIG_REFERENCE.md)

Plugin Configuration

Plugin Discovery

Plugins are automatically discovered from the plugin-repos directory. Each plugin should have:

  • manifest.json: Plugin metadata and configuration schema
  • manager.py: Plugin implementation
  • requirements.txt: Plugin dependencies

Plugin Configuration in config.json

Plugins are configured by adding their plugin ID as a top-level key in the config:

{
    "weather": {
        "enabled": true,
        "api_key": "your_api_key",
        "update_interval": 1800,
        "units": "imperial"
    },
    "stocks": {
        "enabled": true,
        "symbols": ["AAPL", "GOOGL", "MSFT"],
        "update_interval": 600
    }
}

Plugin Display Durations

Add plugin display modes to the display_durations section:

{
    "display": {
        "display_durations": {
            "calendar": 30,
            "weather": 30,
            "weather_forecast": 30,
            "stocks": 30,
            "stock_news": 20
        }
    }
}

Migration from Old Configuration

Removed Sections

The following configuration sections have been removed as they are now handled by plugins:

  • All sports manager configurations (NHL, NBA, NFL, etc.)
  • Weather manager configuration
  • Stock manager configuration
  • News manager configuration
  • Music manager configuration
  • All other content manager configurations

What Remains

Only core system components remain in the main configuration:

  • Display hardware settings
  • Schedule settings
  • Calendar manager (core component)
  • Plugin system settings

Plugin Development

Plugin Structure

Each plugin should follow this structure:

plugin-repos/
└── my-plugin/
    ├── manifest.json
    ├── manager.py
    ├── requirements.txt
    └── README.md

Plugin Manifest

{
    "id": "my-plugin",
    "name": "My Plugin",
    "version": "1.0.0",
    "description": "Plugin description",
    "author": "Your Name",
    "entry_point": "manager.py",
    "class_name": "MyPlugin",
    "display_modes": ["my_plugin"]
}

The Plugin Store refuses a manifest that lacks any of id, name, class_name or display_modes (store_manager.py); the loader itself needs class_name. version is not required, but the store compares it with the registry's latest_version to offer updates, so set it. entry_point defaults to manager.py if omitted. The config schema is not named in the manifest: it is always the file config_schema.json in the plugin directory. The class_name value must match the actual class defined in the entry point file exactly (case-sensitive, no spaces); otherwise the loader fails with a PluginError ("Class ... not found in module") at load time.

Plugin Manager Class

from src.plugin_system.base_plugin import BasePlugin

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.config, self.display_manager, self.cache_manager,
        # self.plugin_manager, self.logger, and self.enabled are
        # all set up by BasePlugin.__init__.

    def update(self):
        """Fetch/update data. Called based on update_interval."""
        pass

    def display(self, force_clear=False):
        """Render plugin content to the LED matrix."""
        pass
    
    # BasePlugin.get_display_duration() already returns
    # self.config['display_duration'] (default 15s); override it only to
    # vary the duration with the content.
    def get_display_duration(self):
        return self.config.get('display_duration', 30)

Dynamic Duration Configuration

Plugins that render multi-step content (scrolling leaderboards, tickers, etc.) can opt-in to dynamic durations so the display controller waits for a full cycle.

{
    "football-scoreboard": {
        "enabled": true,
        "dynamic_duration": {
            "enabled": true,
            "max_duration_seconds": 240
        }
    },
    "display": {
        "dynamic_duration": {
            "max_duration_seconds": 180
        }
    }
}
  • Set dynamic_duration.enabled per plugin to toggle the behaviour.
  • Optional dynamic_duration.max_duration_seconds on the plugin overrides the global cap (defined under display.dynamic_duration.max_duration_seconds, default 180s).
  • Plugins should override supports_dynamic_duration(), is_cycle_complete(), and reset_cycle_state() (see BasePlugin) to control when a cycle completes.

Configuration Tabs

Each installed plugin automatically gets its own dedicated configuration tab in the web interface. This provides a clean, organized way to configure plugins.

Accessing Plugin Configuration

  1. Navigate to the Plugin Manager tab to see all installed plugins
  2. Click the Configure button on any plugin card, or
  3. Click directly on the plugin's tab button in the navigation bar

Auto-Generated Forms

Configuration forms are automatically generated from each plugin's config_schema.json:

  • Boolean → Toggle switch
  • Number/Integer → Number input with min/max validation
  • String → Text input with length constraints
  • Array → Comma-separated input
  • Enum → Dropdown menu

Configuration Features

  • Type-safe inputs: Form inputs match JSON Schema types
  • Default values: Fields show current values or schema defaults
  • Real-time validation: Input constraints enforced (min, max, maxLength, etc.)
  • Help text: Each field shows description from schema

For more details, see Plugin Configuration Tabs.

For information about how core properties (enabled, display_duration, live_priority) are handled, see Core Plugin Properties.

Schema Validation

The configuration system uses JSON Schema Draft-07 for validation:

  • Pre-save validation: Invalid configurations are rejected before saving
  • Automatic defaults: Default values extracted from schemas
  • Error messages: Clear error messages show exactly what's wrong
  • Reliable loading: Schema loading with caching and fallback paths
  • Core properties handling: System-managed properties (enabled, display_duration, live_priority) are automatically handled - they don't need to be in plugin schemas and aren't validated as required fields

Schema Structure

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "enabled": {
      "type": "boolean",
      "default": true,
      "description": "Enable or disable this plugin"
    },
    "update_interval": {
      "type": "integer",
      "default": 3600,
      "minimum": 60,
      "maximum": 86400,
      "description": "Update interval in seconds"
    }
  }
}

Best Practices

  1. Keep main config minimal: Only include core system settings
  2. Use plugin-specific configs: Each plugin manages its own configuration
  3. Document plugin requirements: Include clear documentation for each plugin
  4. Version control: Keep plugin configurations in version control
  5. Testing: Test plugins in emulator mode before hardware deployment
  6. Use schemas: Always provide config_schema.json for your plugins
  7. Sensible defaults: Ensure defaults work without additional configuration
  8. Add descriptions: Help users understand each setting

Troubleshooting

Common Issues

  1. Plugin not loading: Check plugin manifest and directory structure
  2. Configuration errors: Validate plugin configuration against schema
  3. Display issues: Check display durations and plugin display methods
  4. Performance: Monitor plugin update intervals and resource usage
  5. Form missing or wrong: Verify config_schema.json exists in the plugin directory and is valid JSON Schema
  6. Settings not saving: Check validation errors and ensure all required fields are filled

Debug Mode

There is no config key for debug logging. Run the display with debug logging instead:

python3 run.py -d            # or: LEDMATRIX_DEBUG=true python3 run.py

See Also