-
Notifications
You must be signed in to change notification settings - Fork 0
Control Server
The custom control server runs on the Pi at port 5000 and provides a richer interface on top of QLC+'s native web UI.
Current version: v2.13.2
Access it at http://lights.local:5000
The control server provides:
- Chat (new default tab) — Full agentic Claude/ChatGPT-style conversation with access to ~39 MCP tools. The LLM plans multi-step operations ("set up three-point lighting and save it as a scene") and executes them server-side. Anthropic or OpenAI; Ollama not yet supported.
- Quick Commands (was AI Control) — One-shot natural language commands ("make it warmer", "party mode", "fade to black over 5 seconds")
-
Virtual Console — Per-fixture channel sliders with correct labels from
.qxfdefinitions, plus an Identify button per fixture (pulses it for physical location) - Scene Management — Activate, rename, duplicate, delete saved scenes via a hover-menu on each scene tile
- Cue Lists (new tab) — Build audio-synced shows, GO/STOP playback, live elapsed-time indicator
- Chases (new tab) — Build QLC+ chases from a scene picker, start/stop via the native chase engine
- Quick Tools (new tab) — Kelvin slider for white balance, strobe rate slider, palette assignment
- Diagnostics (new tab) — Pi health (CPU temp / load / memory / disk / uptime / USB / service status), test_dmx button, systemd log tail
- Fixture Groups — Create / rename / delete groups inline from the drawer, organize fixtures into named zones, target commands to specific groups
- BLACKOUT — Always-visible header button. Zeros every channel including strobe/macro state.
- Health Dashboard — Live status of QLC+, WebSocket connection, AI provider, and workspace
The UI was redesigned in v2.13 around a minimal, console-inspired aesthetic:
- Geist Sans + Geist Mono for body and code respectively
-
CSS token system on
:root—--ink,--paper,--rule,--amber-tungsten,--signal-*— single source of truth for colors -
Amber-tungsten accent (
#ffa257) for active/selected states, evoking a warm filament - Filament dot indicator in the header (live status pulse)
- Animated tab indicator slides under the active tab
-
Eyebrow labels on tool panels via
data-eyebrowattribute (small uppercase context labels) - BLACKOUT button redesigned — always-visible header button, high-contrast red, single click zeros every channel including strobe and macro state
The active tab is mirrored to the URL as ?tab=<name> via history.replaceState, so:
- Refresh lands you back on the same tab
- Tab URLs are bookmarkable and shareable
- The default landing tab (when no
?tab=is present) is Quick Commands — changed from Chat in v2.13.1
Valid values: ?tab=chat, ai, console, cuelists, chases, tools, diag.
The UI is mobile-first and installable as a Progressive Web App on iOS and Android. See Mobile-and-PWA for installation steps, what works offline, and the mobile-specific UX (44px touch targets, full-screen modals, horizontal-scroll tabs row, edge-to-edge chat composer).
./lightsctl.sh control-installThis creates a Python venv on the Pi, installs dependencies, and sets up lighting-control.service.
The server maintains a single persistent WebSocket to QLC+ on a dedicated background thread. All HTTP requests dispatch channel commands through this one connection. This avoids the CLOSE_WAIT socket leak that occurs when each request opens its own short-lived connection (QLC+ 4.14.x has a hard limit of ~50 concurrent WebSocket clients).
See CONTROL_SERVER_ARCHITECTURE.md for the full technical deep-dive.
| Method | Path | Purpose |
|---|---|---|
| POST | /api/command |
AI natural-language command (one-shot interpreter) |
| POST | /api/chat |
Agentic chat — full tool-use loop, ~39 tools, Anthropic or OpenAI |
| POST | /api/action |
Structured action dispatch (used by MCP-Server, bypasses AI interpreter) |
| POST | /api/batch |
Execute an ordered list of actions in one request |
| POST | /api/blackout |
Instant kill-all — zeroes every channel on targeted fixtures |
| GET | /api/status |
Multi-service health JSON |
| GET | /api/scenes |
List workspace scenes |
| GET | /api/scenes/<id> |
Describe a saved scene (per-fixture channel breakdown) |
| POST | /api/scenes/<id>/activate |
Apply a scene live |
| POST | /api/scenes/save |
Save scene XML to workspace permanently |
| POST | /api/scenes/snapshot |
Capture current state as a new scene |
| POST | /api/scenes/<id>/duplicate |
Clone a saved scene under a new name |
| PATCH | /api/scenes/<id> |
Rename a scene (and/or move its Path folder) |
| DELETE | /api/scenes/<id> |
Delete a saved scene |
| GET | /api/fixtures |
List fixtures with channel_info |
| GET | /api/fixture_channels/<id> |
Per-fixture channel breakdown |
| POST | /api/fixtures/<id>/identify |
Flash a fixture so you can locate it physically |
| GET | /api/groups |
List fixture groups |
| POST | /api/groups |
Create a new group |
| PATCH | /api/groups/<name> |
Rename / re-describe / replace fixture list |
| DELETE | /api/groups/<name> |
Delete a group |
| POST | /api/groups/<name>/fixtures |
Append fixtures to a group |
| DELETE | /api/groups/<name>/fixtures |
Remove fixtures from a group |
| POST | /api/channel |
Set a single channel value |
| GET | /api/channel_values |
Live DMX values from QLC+ |
| POST | /api/diagnostics/test_dmx |
R → G → B → restore sweep across fixtures |
| GET | /api/diagnostics/logs/<service> |
Tail systemd journal for an allowlisted service |
| GET | /api/diagnostics/system |
Pi-level health JSON |
| GET | /api/chases |
List chases in the workspace |
| GET | /api/chases/<id> |
Describe a chase (steps, scene names, timing) |
| POST | /api/chases |
Create a new chase |
| DELETE | /api/chases/<id> |
Remove a chase |
| POST | /api/chases/<id>/start |
Start chase playback |
| POST | /api/chases/<id>/stop |
Stop chase playback |
| GET | /api/cue_lists |
List saved cue lists (with runtime status) |
| GET | /api/cue_lists/active |
Currently-playing cue lists, with elapsed time |
| GET | /api/cue_lists/<id> |
Describe a cue list |
| POST | /api/cue_lists |
Create a new cue list |
| PATCH | /api/cue_lists/<id> |
Rename / re-describe / replace cues array |
| DELETE | /api/cue_lists/<id> |
Remove (stops playback first if running) |
| POST | /api/cue_lists/<id>/go |
GO — start playback |
| POST | /api/cue_lists/<id>/stop |
Halt running cue list |
The save feature lets you persist any AI-generated scene (or the current live state) into the QLC+ workspace permanently:
# Via API
curl -X POST http://lights.local:5000/api/scenes/save \
-H "Content-Type: application/json" \
-d '{"name": "My Scene", "snapshot": true}'
# Or with scene XML from a previous generate
curl -X POST http://lights.local:5000/api/scenes/save \
-H "Content-Type: application/json" \
-d '{"name": "Warm Sunset", "scene_xml": "<Function Type=\"Scene\" ...>...</Function>"}'Saved scenes appear in the Scenes tab immediately and persist through reboots.
The server reads QLC+ .qxf fixture definition files to resolve each channel's semantic role. This means:
- Channel sliders show correct labels (e.g. "Warm White" not "Ch 2")
- The AI knows which channels are color vs strobe vs configuration
- Color commands drive the right channels per fixture type
The parser checks /usr/share/qlcplus/fixtures/ (system) and ~/.qlcplus/fixtures/ (user overrides).
To expose the same control surface to MCP-capable LLM agents (Claude Desktop, ChatGPT, Cursor, custom clients), install the sibling MCP server — see MCP-Server. It runs at port 5001 and calls back into this server's /api/action endpoint.
./lightsctl.sh control-status # Service status
./lightsctl.sh control-logs # Recent logs
./lightsctl.sh control-restart # Restart the server
./lightsctl.sh control-uninstall # Remove the server
./lightsctl.sh env-sync # Push .env to Pi and restart