* fix(web): on-demand no longer restarts a running display service POST /display/on-demand/start treated start_service (default true, sent by "Preview on display", the on-demand dialog and the MQTT bridge) as "restart": with the service running it ran systemctl stop, slept 1.5s and started it again. Every request cold-started the display process -- every plugin reloaded, panel blank -- to deliver a request the running process already reads from the cache mailbox every ON_DEMAND_POLL_INTERVAL (0.25s), including mid-dwell, mid-screen and mid-Vegas. The restart bought nothing: startup only restores a session the display saved itself (display_on_demand_config), so the new request arrived through the same mailbox either way. start_service now means "start it if it is not running". The stop route coerces stop_service to a boolean so "false" no longer stops the service. test_api_v3_on_demand_restart.py pinned the old restart path; it now pins the replacement. Docs updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(display): on-demand loads a disabled plugin live instead of failing The display process only loads enabled plugins, so an on-demand request for a disabled one -- "Preview on display" offers it on every config page, with a note that the plugin will be enabled for the preview -- failed with invalid-mode. Nothing enabled it short of a restart, and the on-demand route no longer restarts the service. _activate_on_demand now loads an installed-but-not-running plugin through the live-enable path (load_plugin + _register_loaded_plugin), with a new load_plugin(force_enabled=True) so the instance runs enabled while config.json keeps saying disabled. The plugin is tracked in _on_demand_loaded_plugins, and the main loop unloads it through _unregister_plugin once on-demand moves off it (stop, expiry, another request, or a failed request that ends the session) -- right after its own poll, where no display() is on the stack. A failed load publishes status error with load-failed. A plugin enabled during the session stays loaded. A session restored after a restart uses the same tracking instead of setting enabled in the config dict config_manager caches, so its plugin is unloaded when the session ends rather than staying loaded until the next restart. Ending a session no longer resumes the rotation onto a plugin that is about to be unloaded, which a restored session did. Also: a stop sent while on-demand is inactive clears a failed request's error, instead of /display/on-demand/status reporting status: error until the state aged out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
LED Matrix Web Interface V3
Modern, production web interface for controlling the LED Matrix display.
Overview
This directory contains the active V3 web interface with the following features:
- Real-time display preview via Server-Sent Events (SSE)
- Plugin management and configuration
- System monitoring and logs
- Modern, responsive UI
- RESTful API
Directory Structure
web_interface/
├── app.py # Main Flask application
├── start.py # Startup script
├── requirements.txt # Python dependencies
├── blueprints/ # Flask blueprints
│ ├── api_v3/ # API endpoints (package: config, display,
│ │ # plugins, system, backup, fonts, misc,
│ │ # wifi, starlark)
│ └── pages_v3.py # Page routes
├── templates/ # HTML templates
│ └── v3/
│ ├── base.html
│ └── partials/
└── static/ # CSS/JS assets
└── v3/
├── app.css
├── app.js
├── manifest.json # PWA manifest
├── plugins_manager.js
├── icons/ # PWA / touch icons
├── js/ # Alpine, htmx, app shell, widgets, utils
└── vendor/ # codemirror, fontawesome
Running the Web Interface
Standalone (Development)
From the project root:
python3 web_interface/start.py
As a Service (Production)
The web interface can run as a systemd service that starts automatically based on the web_display_autostart configuration setting:
sudo systemctl start ledmatrix-web
sudo systemctl enable ledmatrix-web # Start on boot
Accessing the Interface
Once running, access the web interface at:
- Local: http://localhost:5000
- Network: http://:5000
Configuration
The web interface reads configuration from:
config/config.json- Main configurationconfig/config_secrets.json- API keys and secrets
API Documentation
The V3 API is the api_v3 blueprint, registered at /api/v3/ in
app.py. For the complete
list and request/response formats, see
docs/REST_API_REFERENCE.md. Quick
reference for the most common endpoints:
Configuration
GET /api/v3/config/main- Get main configurationPOST /api/v3/config/main- Save main configurationGET /api/v3/config/secrets- Get secrets configurationPOST /api/v3/config/raw/main- Save raw main config (Config Editor)POST /api/v3/config/raw/secrets- Save raw secrets
Display & System Control
GET /api/v3/system/status- System statusPOST /api/v3/system/action- Control display (action body:start_display,stop_display,restart_display_service,restart_web_service,git_pull,reboot_system,shutdown_system,enable_autostart,disable_autostart)GET /api/v3/display/current- Current display frameGET /api/v3/display/on-demand/status- On-demand statusPOST /api/v3/display/on-demand/start- Trigger on-demand displayPOST /api/v3/display/on-demand/stop- Clear on-demand
Plugins
GET /api/v3/plugins/installed- List installed pluginsGET /api/v3/plugins/config?plugin_id=<id>- Get plugin configPOST /api/v3/plugins/config- Update plugin configurationGET /api/v3/plugins/schema?plugin_id=<id>- Get plugin schemaPOST /api/v3/plugins/toggle- Enable/disable pluginPOST /api/v3/plugins/install- Install from registryPOST /api/v3/plugins/install-from-url- Install from GitHub URLPOST /api/v3/plugins/uninstall- Uninstall pluginPOST /api/v3/plugins/update- Update plugin
Plugin Store
GET /api/v3/plugins/store/list- List available registry pluginsGET /api/v3/plugins/store/github-status- GitHub authentication statusPOST /api/v3/plugins/store/refresh- Refresh registry from GitHub
Real-time Streams (SSE)
SSE stream endpoints are defined directly on the Flask app in app.py
(stream_stats, stream_display, stream_logs, followed by their CSRF
exemption and rate-limit hookup), not on the api_v3 blueprint:
GET /api/v3/stream/stats- System statistics streamGET /api/v3/stream/display- Display preview streamGET /api/v3/stream/logs- Service logs stream
Development
When making changes to the web interface:
- Edit files in this directory
- Test changes by running
python3 web_interface/start.py - Restart the service if running:
sudo systemctl restart ledmatrix-web
Notes
- Templates and static files use the
v3/prefix to allow for future versions - The interface uses Flask blueprints for modular organization
- SSE streams provide real-time updates without polling