mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-04 14:25:08 +00:00
* chore: mark skins unsupported, fix stale docs and preview size, prepare 3.4.0
Skins: no current scoreboard plugin builds on src.base_classes, so the only
skin hook (SportsCore._render_game) never runs. The plugin schema endpoint no
longer injects the Visual Skin dropdown, the store hides and refuses
"type": "skin" registry entries, and GET /api/v3/skins reports
supported: false with a message. Stored skin config still loads and saves.
src/skin_system/ and its tests are unchanged apart from the support flag.
Docs: check_plugin.py/render_plugin.py examples use --plugin; document
BasePlugin.get_update_interval() and its interaction with the manifest
update_interval; CLAUDE.md drops the stale template line number and
recommends display_manager.width/height.
Preview size: new src/display_geometry.py holds the size computation and
defaults DisplayManager uses (double-sided applied, chain_length default 2).
The web preview, /display/current, Starlark magnify default, sync handshake
and two dev scripts use it.
Release: __version__ 3.4.0, CHANGELOG 3.4.0 section plus a 3.3.0 tag note.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix: address CodeRabbit review on #580
- Preview fallbacks (SSE stream and /display/current) use logical_size({})
(128x32, the shared default) instead of a hard-coded 128x64.
- display_geometry treats a non-mapping display/hardware block as missing,
so a malformed config.json falls back to defaults instead of raising
AttributeError (which turned the Starlark render into an HTTP 500).
- Docs: the static update interval falls back manifest -> plugin config
-> 60s, in both the API reference and the architecture spec.
Not taken: validating double_sided copies against chain_length/parallel.
An orientation Rotate: or U-mapper pixel mapper decides which axis panels
lie on, so the counts would reject working setups (the existing
vertical-split test is one).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(display_geometry): a non-finite hardware size raises ValueError, not OverflowError
CodeRabbit flagged the Starlark magnify default in
_standalone_render_starlark_app for truthy non-mapping display values. That
case was already handled by a9e1bd0b (_display/_hardware treat a non-mapping
block as missing, covered by test_non_mapping_display_config_uses_the_defaults),
and the magnify it produces from the 128x32 defaults is the same as from 64x32.
Checking the same path found one input that still escaped: Python's JSON
parser accepts Infinity, and int(inf) raises OverflowError, which neither the
Starlark path (TypeError, ValueError) nor the preview stream in app.py caught,
so a hand-edited "rows": Infinity returned HTTP 500. physical_size now raises
ValueError for it, matching its documented contract, so every caller's
existing fallback applies. DisplayManager already caught Exception.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
66 lines
5.2 KiB
Markdown
66 lines
5.2 KiB
Markdown
# LEDMatrix
|
|
|
|
## Project Structure
|
|
- `src/plugin_system/` — Plugin loader, manager, store manager, base plugin class
|
|
- `web_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 by `plugin_system.plugins_directory` in
|
|
`config.json` (default per `config/config.template.json`).
|
|
Not gitignored.
|
|
- `plugins/` — Legacy/dev plugin location. Gitignored (`plugins/*`).
|
|
Used by `scripts/dev/dev_plugin_setup.sh` for 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
|
|
(`StoreManager._find_plugin_path()` in `store_manager.py`) and schema
|
|
lookup (`SchemaManager.get_schema_path()` in `schema_manager.py`,
|
|
which probes `plugins/` *before* `plugin-repos/`).
|
|
|
|
## Plugin System
|
|
- Plugins inherit from `BasePlugin` in `src/plugin_system/base_plugin.py`
|
|
- Required abstract methods: `update()`, `display(force_clear=False)`
|
|
- Each plugin needs: `manifest.json`, `config_schema.json`, `manager.py`, `requirements.txt`
|
|
- 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` — not `display_manager.matrix.width/height`, because `matrix` is `None` when 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": true` in the plugin's config schema, and deep-merged into
|
|
the plugin's config dict at load time — plugins read them with plain
|
|
`config.get(...)`, never a separate accessor
|
|
|
|
## Dev Workflow
|
|
- Link a plugin for development: `./scripts/dev/dev_plugin_setup.sh link-github <name>` (or `link <name> <path>`); symlinks land in `plugins/` — set `plugin_system.plugins_directory` to `plugins` so discovery picks them up
|
|
- Browser preview without the display loop: `python3 scripts/dev_server.py` → http://localhost:5001
|
|
- Full display in emulator mode: `python3 run.py -e` (or `EMULATOR=true python3 run.py`)
|
|
- Validate one plugin headlessly: `python3 scripts/check_plugin.py --plugin <id>`
|
|
|
|
## Plugin Store Architecture
|
|
- Official plugins live in the `ledmatrix-plugins` monorepo (not individual repos)
|
|
- Plugin repo naming convention: `ledmatrix-<plugin-id>` (e.g., `ledmatrix-football-scoreboard`)
|
|
- `plugins.json` registry at `https://raw.githubusercontent.com/ChuckBuilds/ledmatrix-plugins/main/plugins.json`
|
|
- Store manager (`src/plugin_system/store_manager.py`) handles install/update/uninstall
|
|
- Monorepo plugins are installed via ZIP extraction (no `.git` directory)
|
|
- 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`
|
|
|
|
## Skin System (visual overlays for sports scoreboards) — NOT SUPPORTED YET
|
|
- Skins do not render with the current scoreboard plugins: the only hook is `SportsCore._render_game()` in `src/base_classes/sports/core.py`, and no current scoreboard plugin (monorepo or third-party registry) builds on `src.base_classes`
|
|
- So core doesn't offer them: no Visual Skin dropdown (`get_plugin_schema` skips `inject_skin_selector`), the store hides/refuses `"type": "skin"` entries, `GET /api/v3/skins` reports `"supported": false`. Switch: `SKINS_RENDER_SUPPORTED` in `src/skin_system/__init__.py`
|
|
- Stored `skin` / `skin_options` config values must keep loading and saving (base schema allows them; form saves deep-merge over the stored section)
|
|
- Skins live in `skins/<skin-id>/` (skin.json + skin.py), NOT in plugin dirs — plugin reinstall deletes plugin dirs
|
|
- Core: `src/skin_system/` (ScoreboardSkin, SkinContext, runtime); keep it and its tests
|
|
- Skins render onto `ctx.canvas` only; fallback to built-in renderer on `False`/exception (3 strikes disables for session)
|
|
- View-model guaranteed keys are frozen (see `test/test_skin_system.py::TestViewModelContract`) — renaming keys in `_extract_game_details_common` or sport extractors breaks published skins
|
|
- Validate skins headlessly: `python scripts/validate_skin.py --skin <id>`; docs: `docs/SKIN_SYSTEM.md`, `docs/CREATING_SKINS.md`
|
|
- Skins are NOT monorepo plugins: no manifest bump / update_registry.py needed
|
|
|
|
## 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
|