Building Lights Pi: a friendlier face for QLC+ in a studio featured image

Building Lights Pi: a friendlier face for QLC+ in a studio

July 03, 2026 · 7 min read

What I learned building Lights Pi: turning a Raspberry Pi running QLC+ into something a teacher or guest in a studio can actually use without learning DMX.

I wanted to set up a studio where anyone walking in could change the lighting. Not “anyone with a half-hour to learn the QLC+ Virtual Console.” Anyone. A teacher swapping between a lecture look and a discussion look mid-class. A guest presenter who’s never touched a lighting board. Me, from my phone, halfway across the room.

QLC+ is great. It’s free, it speaks DMX, and on a Raspberry Pi it’ll happily drive an ENTTEC USB Pro into whatever fixtures you’ve got. But QLC+’s own web interface is a designer’s tool. It assumes you know what a universe is, what a fixture profile looks like, and which channels do what. The whole point of a classroom studio is that the person at the controls shouldn’t have to know any of that.

So I started layering things on top of QLC+ on the Pi. That snowballed into Lights Pi: a setup-and-control toolkit that takes a fresh Pi from blank SD card to working DMX controller, plus the wrappers around QLC+ that I actually wanted to have.

The shape of the problem

A headless QLC+ install on a Pi sounds simple, and the first 80% is. You flash an SD card, install qlcplus, run it with the right Qt platform flags so it doesn’t try to open a window, point it at the ENTTEC, and call it a day.

The other 20% is what eats your weekend. The service crash-loops if you forget to set QT_QPA_PLATFORM=minimal. The USB device shows up as /dev/ttyUSB0 one boot and /dev/ttyUSB1 the next. The Pi loses WiFi for ten minutes when someone microwaves something nearby and never reconnects. mDNS works on macOS but not on the Android phone you wanted to control it from. And then a class is about to start and the lights are off.

I ended up writing the same fixes over and over across rebuilds, and at some point it was obvious that the fixes were the project. The QLC+ part was the easy bit.


One command line tool, one Pi

The core of Lights Pi is a bash script, lightsctl.sh, that you run from your laptop. It SSHes into the Pi and does the thing. Provisioning, hardening, deploying a QLC+ workspace, pulling one back down for git, backing up the config, running diagnostics, scanning the network when mDNS gives up. One verb per subcommand, no magic.

# Fresh Pi to working controller (set WiFi creds, run setup)
WIFI1_SSID="SetupNet" WIFI1_PSK="setup-pass" WIFI2_SSID="StudioNet" WIFI2_PSK="studio-pass" ./lightsctl.sh setup-full

# Then verify
./lightsctl.sh doctor
./lightsctl.sh test-dmx

The setup-full command does the boring things in the right order: sets the hostname, configures dual WiFi with priority (studio network wins when it’s available), installs QLC+ with retry-on-flake, registers the systemd service with the right Qt flags, adds the user to dialout so it can see the ENTTEC, and writes a udev rule so the ENTTEC always lands at /dev/dmx0 no matter which USB port it’s plugged into. On a Pi 3 it also disables Bluetooth, drops GPU memory, and switches the CPU governor to performance mode, because the Pi 3 needs every spare watt for real-time DMX timing.

Then doctor tells you what’s broken in plain English. Service down. USB not detected. Web UI not responding. Disk getting full. CPU thermally throttled. The kind of thing where the only useful debugging output is “here is the thing to fix, here is the command that fixes it.”


It’s not just a bash script anymore

The original idea was that the bash script was the whole thing. You’d run QLC+’s own web UI for everything else. That worked for me. It didn’t work the first time I handed it to a teacher.

The QLC+ Virtual Console is a powerful, dense, fixture-aware interface that doesn’t care about you. You have to design the buttons. You have to know which scene goes where. If nobody has done that design work for the room you’re in, it’s a wall of grey.

So Lights Pi grew two more pieces. First, a tiny nginx landing page at the Pi’s root URL with one button that says “Lighting Control” and goes to QLC+. That sounds silly until you’ve watched a substitute teacher type http://lights.local:9999/ into a browser and give up at the colon. https://lights.local/ with a real button is a different experience.

Second, a separate control server: a small Python web app that runs on port 5000 and lets you say “make it warmer” or “party mode” in plain text, and translates that into the right DMX values for whatever fixtures are wired up. It can call out to Anthropic, OpenAI, or a local Ollama model, depending on what you’ve got. There’s also a templates feature that ships pre-built looks with no AI in the loop at all – “youtube-studio”, “ambient”, “warm-white” – because sometimes you just want a button that turns the lights on.

# From your laptop
./lightsctl.sh generate-scene "warm sunset ambiance" --preview
./lightsctl.sh generate-from-template youtube-studio --add-to-workspace

# Or open the natural-language control panel in a browser
./lightsctl.sh control-install
# Then visit http://lights.local:5000

Fixture groups are the piece that ties it together. You name a set of fixture IDs as “key-lights” or “stage-left”, and then “make the key lights warmer” actually means something concrete. The groups round-trip into QLC+’s own workspace XML, so they show up in QLC+’s UI too if anyone wants to drop into the designer.


The WiFi watchdog is the unsexy hero

Half of the headless-Pi-in-a-studio horror stories are WiFi stories. Pi associates with the wrong AP. Pi associates with the right AP but never gets DHCP. Pi was fine yesterday and isn’t responding now. Reboot fixes it, except you can’t get to the Pi to reboot it.

Lights Pi installs an optional WiFi watchdog as a systemd timer. Every two minutes it checks that wlan0 has an IP and can hit the default gateway. If it can’t, it forces a reconnect. If three of those fail in a row, it restarts NetworkManager. It logs everything to a flat file you can read with ./lightsctl.sh wifi-watchdog-logs.

This is the boring kind of feature that nobody asks for in the spec but that turns “the lights are dead” calls into a non-event. I’d rather have ten of those than one more AI feature.


What’s next

The two things I’m chewing on now are an MCP server (so an AI assistant can drive the lighting alongside everything else it’s doing) and a proper marketing site with an interactive demo. The MCP piece in particular feels like the natural next step: the control server already speaks “natural language in, DMX out”, and exposing that as MCP tools means it composes with whatever else you’re running.

I’m sure I’ll write a follow-up when the MCP side ships. In the meantime, if you’ve got an ENTTEC and a Pi sitting in a drawer and you’ve been meaning to do something with DMX, ./lightsctl.sh setup-full will take you most of the way. The repo is at github.com/gfargo/lights-pi and the project page is here.

If you build something on top of this, or you find a fixture profile QLC+ doesn’t know about and want help adding it, the GitHub issues are open. Suggestions and pull requests welcome.

Griffen Fargo headshot

Griffen Fargo

Published

Share
Keep Reading

Discussion

Have thoughts? Drop them in.

Comments are powered by Disqus. Sign in once, comment anywhere.

Loading comments…
Fin.

griffen.codes

made with 💖 and

© 2026all rights reservedupdated 20 seconds ago