mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-04 06:15:09 +00:00
src/common/frame_timing.py times every frame the display presents, whoever drew it, and writes cumulative counters to /dev/shm. scripts/frame_soak.py grades a running service (late frames, freezes, where the time goes) and scripts/render_bench.py the hardware and render path alone. A stall watchdog logs the stacks behind any scroll held up for 250 ms or more (LEDMATRIX_STALL_WATCHDOG_MS lowers that). See docs/SCROLL_PERFORMANCE.md, "Soaking a rig". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5.1 KiB
5.1 KiB
LEDMatrix
Project Structure
src/plugin_system/— Plugin loader, manager, store manager, base plugin classweb_interface/— Flask web UI (blueprints, templates, static JS)config/config.json— User plugin configuration (persists across plugin reinstalls)plugin-repos/— Default plugin install directory used by the Plugin Store, set byplugin_system.plugins_directoryinconfig.json(default perconfig/config.template.json). Not gitignored.plugins/— Legacy/dev plugin location. Gitignored (plugins/*). Used byscripts/dev/dev_plugin_setup.shfor symlinks. The plugin loader does NOT fall back to it —PluginManager.discover_plugins()(src/plugin_system/plugin_manager.py) scans only the configured directory. Fallbacks exist in two narrower places: store operations (PluginStoreManager._find_plugin_path()instore_manager.py, which searchesstore_search_dirs()fromplugin_dirs.py) and schema lookup (SchemaManager.get_schema_path()inschema_manager.py, which probesplugins/beforeplugin-repos/).src/plugin_system/plugin_dirs.py— the one resolver for "which directory holds plugin X" (manifestidfirst, then<id>/ledmatrix-<id>)
Plugin System
- Plugins inherit from
BasePlugininsrc/plugin_system/base_plugin.py - Required abstract methods:
update(),display(force_clear=False) - Each plugin needs:
manifest.json,config_schema.json, and the entry point (manager.pyby default);requirements.txtif it has dependencies. Required manifest fields:docs/PLUGIN_API_REFERENCE.md#manifest-required-fields - 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.width/height— notdisplay_manager.matrix.width/height, becausematrixisNonewhen hardware init fails (the properties fall back to the canvas size) - Secrets: namespaced by plugin id in
config/config_secrets.json, declared via"x-secret": truein the plugin's config schema, and deep-merged into the plugin's config dict at load time — plugins read them with plainconfig.get(...), never a separate accessor
Dev Workflow
- Link a plugin for development:
./scripts/dev/dev_plugin_setup.sh link-github <name>clones theledmatrix-pluginsmonorepo into~/.ledmatrix-dev-plugins/and links itsplugins/<name>under the manifest id (add a repo URL for a plugin with its own repo; orlink <name> <path>); symlinks land inplugins/— setplugin_system.plugins_directorytopluginsso discovery picks them up. Fork/location overrides:dev_plugins.json(fromdev_plugins.json.example) - Browser preview without the display loop:
python3 scripts/dev_server.py→ http://localhost:5001 - Full display in emulator mode:
python3 run.py -e(orEMULATOR=true python3 run.py) - Validate one plugin headlessly:
python3 scripts/check_plugin.py --plugin <id> - Soak a rig for frame timing (on the Pi, service running):
python3 scripts/frame_soak.py --preview— late-frame rate across every scroller; seedocs/SCROLL_PERFORMANCE.md
Plugin Store Architecture
- Official plugins live in the
ledmatrix-pluginsmonorepo (not individual repos) - Plugin repo naming convention:
ledmatrix-<plugin-id>(e.g.,ledmatrix-football-scoreboard) plugins.jsonregistry athttps://raw.githubusercontent.com/ChuckBuilds/ledmatrix-plugins/main/plugins.json- Store manager (
PluginStoreManagerinsrc/plugin_system/store_manager.py) handles install/update/uninstall - Monorepo plugins are installed without a
.gitdirectory: GitHub Trees API + raw downloads, falling back to ZIP extraction - Update detection for monorepo plugins uses version comparison (manifest version vs registry latest_version)
- Plugin configs stored in
config/config.json, NOT in plugin directories — safe across reinstalls - Third-party plugins can use their own repo URL with empty
plugin_path
Common Pitfalls
- paho-mqtt 2.x requires a
CallbackAPIVersionargument:VERSION1for code written against v1 callback signatures (the MQTT bridge usesVERSION2) - BasePlugin uses
get_logger()fromsrc.logging_config, not standardlogging.getLogger() DisplayManagerhas nodraw_image()— paste onto the PIL image directly:self.display_manager.image.paste(img, (x, y))thenupdate_display()(use a mask for transparency:image.paste(rgba, (x, y), rgba))- When modifying a plugin in the monorepo, you MUST bump
versionin itsmanifest.jsonand runpython update_registry.py— otherwise users won't receive the update src/pi5_matrix_support.pyhardcodes what the pinnedrpi-rgb-led-matrix-mastercan drive on a Raspberry Pi 5 (Rp1PioConfigSupported()inlib/rp1/rp1_pio_backend.cc). Re-check it whenever the submodule is bumped: a stale rule blocks Pi 5 settings the new library supports, and a missing one lets the display service crash-loop.src/matrix_support.pyholds the same kind of rules for every board (rows, chain length, mapping names, parallel per mapping) and needs the same re-check