Documentation
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
Overview
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
Visual Identity (v2.13)
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
Tab Persistence (v2.13.2)
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.
Mobile + PWA
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).
Installation
./lightsctl.sh control-installThis creates a Python venv on the Pi, installs dependencies, and sets up lighting-control.service.
Architecture
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.
API Endpoints
| 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 |
Saving Scenes
The save feature lets you persist any AI-generated scene (or the current live state) into the QLC+ workspace permanently:
1# Via API
2curl -X POST http://lights.local:5000/api/scenes/save \
3 -H "Content-Type: application/json" \
4 -d '{"name": "My Scene", "snapshot": true}'
5
6# Or with scene XML from a previous generate
7curl -X POST http://lights.local:5000/api/scenes/save \
8 -H "Content-Type: application/json" \
9 -d '{"name": "Warm Sunset", "scene_xml": "<Function Type=\"Scene\" ...>...</Function>"}'Saved scenes appear in the Scenes tab immediately and persist through reboots.
Fixture Definitions
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).
LLM Agent Access
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.
Management Commands
1./lightsctl.sh control-status # Service status
2./lightsctl.sh control-logs # Recent logs
3./lightsctl.sh control-restart # Restart the server
4./lightsctl.sh control-uninstall # Remove the server
5./lightsctl.sh env-sync # Push .env to Pi and restartWas this page helpful?