Optional web login, off by default: a device that sets no password behaves exactly as before. Set under General > Security; then every page and API route needs a session login or an API token (Authorization: Bearer). Loopback, the Wi-Fi setup flow in AP mode, static files, captive-portal probes and a reduced /api/v3/health stay open. Secrets live in the web_auth section of config_secrets.json and no API returns them. scripts/reset_web_password.py turns login off. Stacked on #674. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Home Assistant MQTT Bridge
Control the matrix from Home Assistant: force any plugin or mode on demand,
turn the display on and off, and set brightness — as real HA entities, not
hand-written mqtt.publish calls.
The bridge owns no display logic. It subscribes to one command topic and
turns each message into a call against the same api_v3 routes the web UI
uses, so behaviour lives in one place. It talks to the API over HTTP only —
no filesystem access — so it can run on the Pi or anywhere that can reach
the web interface.
What appears in Home Assistant
On connect the bridge publishes MQTT Discovery config, so the matrix shows up under Settings → Devices & Services → MQTT with no YAML:
| Entity | Does |
|---|---|
select.ledmatrix_display_mode |
Every mode across enabled plugins. Choosing one force-displays it. |
button.ledmatrix_stop_display |
Back to normal rotation. |
switch.ledmatrix_power |
Starts/stops the display service. |
number.ledmatrix_brightness |
0–100. |
State is read back from the API every 30 seconds, so the entities also track changes made from the web UI or an on-demand window expiring on its own. All four share an availability topic that is the bridge's MQTT last will: if the bridge dies, HA greys the controls out rather than leaving them looking live but inert.
Raw commands
For anything the entities do not cover, publish JSON to the command topic
(ledmatrix/command by default):
// Force a mode. plugin_id is optional — the bridge fills it in from
// /api/v3/display/modes.
{"action": "display", "mode": "nfl_live"}
// duration is seconds; pinned holds this one mode instead of rotating
// through every mode the plugin owns. Pin Starlark apps, where each mode
// is an unrelated widget; leave a sports plugin unpinned so live/recent/
// upcoming still cycle.
{"action": "display", "plugin_id": "starlark-apps", "mode": "aquarium",
"duration": 300, "pinned": true}
{"action": "stop_display"}
{"action": "power", "state": "on"}
{"action": "brightness", "value": 75}
// Re-read the mode list and re-publish discovery, after installing a plugin
{"action": "refresh"}
Every command publishes its outcome to <command_topic>/status, and current
state to <command_topic>/state.
Requirements
- A LEDMatrix install with its web interface reachable (default
http://localhost:5000) - An MQTT broker that Home Assistant is also connected to
- Python 3 with
paho-mqtt2.x andrequests
Install
sudo ./scripts/install/install_mqtt_bridge.sh
That copies bridge_config.example.json to bridge_config.json on first
run, installs the dependencies, and enables ledmatrix-mqtt-bridge.service.
Edit the config with your broker details and re-run it.
{
"mqtt_host": "192.168.1.10",
"mqtt_port": 8883,
"mqtt_username": "ledmatrix",
"mqtt_password": null,
"mqtt_topic": "ledmatrix/command",
"mqtt_tls": true,
"ledmatrix_api_base": "http://localhost:5000"
}
TLS is on by default. Without it the broker password and every display
command cross the network in cleartext. If your broker only listens on plain
1883 — which the Mosquitto add-on does out of the box — set "mqtt_tls": false
and "mqtt_port": 1883. The bridge logs a warning at startup when a password
is configured without TLS.
bridge_config.json is gitignored. Any key can also be supplied through the
environment as LEDMATRIX_MQTT_<KEY> (LEDMATRIX_MQTT_MQTT_PASSWORD, say),
which keeps a broker password out of a file on disk — put it in a systemd
drop-in with Environment= or EnvironmentFile= instead.
Web login. If the web interface's optional login is on (General >
Security), a bridge running on the Pi itself still needs nothing: requests from
the Pi are never asked to log in. A bridge on another machine needs an API
token: create one under General > Security and set "ledmatrix_api_token"
(or LEDMATRIX_MQTT_LEDMATRIX_API_TOKEN, or the token field in the Tools tab's
bridge settings). It is sent as Authorization: Bearer <token>.
Set mqtt_tls: true for a broker with TLS. mqtt_tls_insecure skips
certificate verification and exists only for a self-signed broker on a
trusted LAN; it logs a warning when used.
To run it in the foreground while setting things up:
python3 integrations/mqtt_bridge/ledmatrix_mqtt_bridge.py --config integrations/mqtt_bridge/bridge_config.json
Notes
- Only one thing can be on-demand at a time — the same constraint the web UI has.
- Forcing a mode restarts the display service, so the panel blanks for a moment.
- The mode list comes from
/api/v3/display/modes, which triggers plugin discovery itself. Discovery is lazy and normally happens because somebody opened the dashboard; without that endpoint a bridge that never does would see an empty list. - Brightness writes
display.hardware.brightnessby posting{"brightness": N}to/api/v3/config/main. A JSON save changes only the keys it sends, so the other display settings are left as they were. The display service's config hot reload notices the change within a few seconds and applies it without a restart (unlessLEDMATRIX_HOT_RELOAD=false; while a dim schedule is dimming the panel, the dim level wins until the dim period ends).