Discoverability: re-export the adaptive layout/image API from src.common (the blessed-helpers package plugin authors already know) — canonical paths stay src.adaptive_layout / src.adaptive_images so nothing breaks. Document it in src/common/README.md and cross-link ADAPTIVE_LAYOUT.md from the developer docs authors actually read (quick reference, API reference, advanced dev, font manager, dev preview, plugin dev guide); ADAPTIVE_LAYOUT.md gains adaptive-images, composite-layouts and preserving-user-customization sections. Compat: PluginLoader now logs one advisory warning (never raises) when a plugin's manifest declares a min LEDMatrix version newer than the running core, checking the min_ledmatrix_version / requires.* / versions[] spellings found in the wild. Guarded against stale core version numbers. src/__init__.py __version__ bumped 1.0.0 -> 3.1.0 to match the latest release tag (v3.1.0) — it had never been updated and the compat check needs a truthful number. NOTE: verify this matches the intended release numbering before the next tag. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5.5 KiB
Dev Preview & Visual Testing
Tools for rapid plugin development without deploying to the RPi.
Dev Preview Server
Interactive web UI for tweaking plugin configs and seeing the rendered display in real time.
The size inputs have a preset dropdown with the harness's standard panel
sizes, and the All Sizes button renders the current config at every
harness size in a side-by-side gallery (POST /api/render-matrix) — the
quickest way to eyeball adaptive-layout behavior across panels
(see ADAPTIVE_LAYOUT.md).
Quick Start
python scripts/dev_server.py
# Opens at http://localhost:5001
Options
python scripts/dev_server.py --port 8080 # Custom port
python scripts/dev_server.py --extra-dir /path/to/custom-plugin # 3rd party plugins
python scripts/dev_server.py --debug # Flask debug mode
Workflow
- Select a plugin from the dropdown (auto-discovers from
plugins/andplugin-repos/) - The config form auto-generates from the plugin's
config_schema.json - Tweak any config value — the display preview updates automatically
- Toggle "Auto" off for plugins with slow
update()calls, then click "Render" manually - Use the zoom slider to scale the tiny display (128x32) up for detailed inspection
- Toggle the grid overlay to see individual pixel boundaries
Mock Data for API-dependent Plugins
Many plugins fetch data from APIs (sports scores, weather, stocks). To render these locally, expand "Mock Data" and paste a JSON object with cache keys the plugin expects.
To find the cache keys a plugin uses, search its manager.py for self.cache_manager.set( calls.
Example for a sports plugin:
{
"football_scores": {
"games": [
{"home": "Eagles", "away": "Chiefs", "home_score": 24, "away_score": 21, "status": "Final"}
]
}
}
CLI Render Script
Render any plugin to a PNG image from the command line. Useful for AI-assisted development and scripted workflows.
Usage
# Basic — renders with default config
python scripts/render_plugin.py --plugin hello-world --output /tmp/hello.png
# Custom config
python scripts/render_plugin.py --plugin clock-simple \
--config '{"timezone":"America/New_York","time_format":"12h"}' \
--output /tmp/clock.png
# Different display dimensions
python scripts/render_plugin.py --plugin hello-world --width 64 --height 32 --output /tmp/small.png
# 3rd party plugin from a custom directory
python scripts/render_plugin.py --plugin my-plugin --plugin-dir /path/to/repo --output /tmp/my.png
# With mock API data
python scripts/render_plugin.py --plugin football-scoreboard \
--mock-data /tmp/mock_scores.json \
--output /tmp/football.png
Using with Claude Code / AI
Claude can run the render script, then read the output PNG (Claude is multimodal and can see images). This enables a visual feedback loop:
Claude → bash: python scripts/render_plugin.py --plugin hello-world --output /tmp/render.png
Claude → Read /tmp/render.png ← Claude sees the actual rendered display
Claude → (makes code changes based on what it sees)
Claude → bash: python scripts/render_plugin.py --plugin hello-world --output /tmp/render2.png
Claude → Read /tmp/render2.png ← verifies the visual change
VisualTestDisplayManager (for test suites)
A display manager that renders real pixels for use in pytest, without requiring hardware.
Basic Usage
from src.plugin_system.testing import VisualTestDisplayManager, MockCacheManager, MockPluginManager
def test_my_plugin_renders_title():
display = VisualTestDisplayManager(width=128, height=32)
cache = MockCacheManager()
pm = MockPluginManager()
plugin = MyPlugin(
plugin_id='my-plugin',
config={'enabled': True, 'title': 'Hello'},
display_manager=display,
cache_manager=cache,
plugin_manager=pm
)
plugin.update()
plugin.display(force_clear=True)
# Verify pixels were drawn (not just that methods were called)
pixels = list(display.image.getdata())
assert any(p != (0, 0, 0) for p in pixels), "Display should not be blank"
# Save snapshot for manual inspection
display.save_snapshot('/tmp/test_my_plugin.png')
Pytest Fixture
A visual_display_manager fixture is available in plugin tests:
def test_rendering(visual_display_manager):
visual_display_manager.draw_text("Test", x=10, y=10, color=(255, 255, 255))
assert visual_display_manager.width == 128
pixels = list(visual_display_manager.image.getdata())
assert any(p != (0, 0, 0) for p in pixels)
Key Differences from MockDisplayManager
| Feature | MockDisplayManager | VisualTestDisplayManager |
|---|---|---|
| Renders pixels | No (logs calls only) | Yes (real PIL rendering) |
| Loads fonts | No | Yes (same fonts as production) |
| Save to PNG | No | Yes (save_snapshot()) |
| Call tracking | Yes | Yes (backwards compatible) |
| Use case | Unit tests (method call assertions) | Visual tests, dev preview |
Plugin Test Runner
The test runner auto-detects plugin-repos/ for monorepo development:
# Auto-detect (tries plugins/ then plugin-repos/)
python scripts/run_plugin_tests.py
# Test specific plugin
python scripts/run_plugin_tests.py --plugin clock-simple
# Explicit directory
python scripts/run_plugin_tests.py --plugins-dir plugin-repos/
# With coverage
python scripts/run_plugin_tests.py --coverage --verbose