Skip to content

Control Server

Griffen Fargo edited this page May 18, 2026 · 9 revisions

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 .qxf definitions, 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-eyebrow attribute (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-install

This 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:

# 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.

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

./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

Clone this wiki locally