Rewrite README for beginners and auto-elevate install.sh

Make the README approachable for non-experts with a jargon glossary and
step-by-step framing. Have install.sh re-exec itself under sudo and infer
the service user from SUDO_USER, prompting when the 'pi' fallback is absent.
This commit is contained in:
James Coleman 2026-06-03 20:41:51 -05:00
parent 635afec441
commit b9fa97faa9
2 changed files with 216 additions and 125 deletions

307
README.md
View file

@ -1,53 +1,92 @@
This project controls the GRAFIK Eye QS control panel via a QSE-CI-NWK-E over its # Lutron GRAFIK Eye QS — DMX & Home Assistant control
serial interface. It uses the [OLA](https://www.openlighting.org/) project to take
a DMX device or a network DMX protocol (e.g. sACN/E1.31) and drive the 6 available
zones. It also speaks MQTT for Home Assistant control, with MQTT auto discovery so
the light appears automatically.
I run this on a Raspberry Pi (a Pi Zero works) on **Raspberry Pi OS / Raspbian 13 This lets you control a **Lutron GRAFIK Eye QS** lighting unit from two places it
(Trixie)**. OLA is no longer packaged for recent Debian/Raspbian releases, so it is normally can't be reached from:
built from source at the `0.10.9` release tag. Just run the included `install-ola.sh`
(see below) — it handles the whole build for you. The script was written based on the
[OLA build guide](https://www.openlighting.org/ola/linuxinstall/), so you don't need
to follow that guide yourself; it's linked only as a reference for what the script does.
DMX and MQTT are independent, optional components. Serial control of the QSE is - **A lighting board / theatrical software** (so the GRAFIK Eye's zones can be run
always active; you can run with DMX only, MQTT only, or both. as part of a larger light show), and
- **Home Assistant** (so you can control and automate the lights like any other
smart light in your house).
It runs as a small always-on program on a **Raspberry Pi** that you wire to the
GRAFIK Eye. The Pi listens for commands and translates them into the GRAFIK Eye's
own language. The two control methods are independent and both optional — use one,
the other, or both.
> **New to the jargon?** Here's the short version:
> - **GRAFIK Eye QS** — the Lutron lighting control unit this drives. It has up to
> 6 dimmable lighting *zones*.
> - **QSE-CI-NWK-E** — the add-on module on the GRAFIK Eye that gives it a serial
> port we can talk to.
> - **DMX / sACN (E1.31)** — the standard "language" lighting boards and stage
> software use. sACN is just DMX sent over your normal network instead of a cable.
> - **OLA (Open Lighting Architecture)** — free software that receives the network
> DMX and hands it to this program. Only needed if you want lighting-board control.
> - **MQTT** — the messaging system Home Assistant uses to talk to devices. Only
> needed if you want Home Assistant control.
## How it's set up here
I run this on a Raspberry Pi (a Pi Zero is plenty) on **Raspberry Pi OS / Raspbian
13 (Trixie)**. Talking to the GRAFIK Eye over serial is always on. DMX and MQTT are
separate optional pieces you can turn on or off in the config file.
One catch with DMX: OLA is no longer pre-packaged for recent Raspberry Pi OS
releases, so it has to be *built from source* (compiled on the Pi). Don't worry —
the included `install-ola.sh` script does the entire build for you. It follows the
official [OLA build guide](https://www.openlighting.org/ola/linuxinstall/), so you
don't have to; that link is there only if you're curious what the script is doing.
# What you'll need # What you'll need
- A **Raspberry Pi** running Raspberry Pi OS (a Pi Zero is enough; a Pi Zero **W** - A **Raspberry Pi** running Raspberry Pi OS. A Pi Zero is enough for serial-only
or any model with networking is needed for sACN/MQTT). These instructions assume control; for sACN or Home Assistant you need networking, so use a Pi Zero **W**
**Raspberry Pi OS / Raspbian 13 (Trixie)**. (Wi-Fi) or any networked model. These instructions assume **Raspberry Pi OS /
- A **USB-to-serial adapter** wired to the QSE-CI-NWK-E's serial terminals (the Raspbian 13 (Trixie)**.
config example uses a Prolific PL2303-style adapter; any 3.3 V / RS-232 adapter - A **USB-to-serial adapter** wired to the QSE-CI-NWK-E's serial terminals. The
that matches your wiring works). example config uses a common Prolific PL2303-style adapter; any 3.3 V / RS-232
- A **GRAFIK Eye QS** with a QSE-CI-NWK-E network/serial interface. adapter that matches your wiring will do.
- For DMX: a lighting console or software sending **sACN/E1.31** on your network. - A **GRAFIK Eye QS** with the **QSE-CI-NWK-E** network/serial interface module.
- For MQTT / Home Assistant: a running **MQTT broker** (the Docker setup below - **For lighting-board control:** a lighting console or software that sends
includes one). **sACN/E1.31** over your network.
- **For Home Assistant:** a running **MQTT broker** (the Docker setup near the end
of this guide includes one).
# Overview # The big picture
The setup is three steps once the Pi is ready: Once your Pi is up and running, setup is just a few steps:
1. **Prepare the Pi and get the code** (step 0) — flash the OS, get a terminal, clone this repo. 1. **Get the Pi ready and download the code** (step 0).
2. **Install OLA** (step 1) — only if you use DMX. This is the slow part (~12 h on a Pi Zero). 2. **Install OLA** (step 1) — *only if you want lighting-board/DMX control.* This is
3. **Install the control service** (step 2) and **configure it** (step 3). the slow part (~12 hours on a Pi Zero, because it compiles from source).
3. **Install the control program** (step 2) and **fill in the config** (step 3).
Everything below is typed into a terminal on the Pi. If you've never used one, the
commands are copy-paste — just swap in your own values where noted.
# Installation # Installation
## 0. Prepare the Pi and get the code ## 0. Get the Pi ready and download the code
If you're starting from scratch, flash **Raspberry Pi OS** with the If you're starting from a blank SD card, flash **Raspberry Pi OS** with the
[Raspberry Pi Imager](https://www.raspberrypi.com/software/). In the imager's [Raspberry Pi Imager](https://www.raspberrypi.com/software/). Before you write the
settings (the gear / "Edit settings"), **set a username and password and enable card, open the imager's settings (the **gear** / **"Edit settings"** button) and:
SSH** — remember the username you choose; you'll use it everywhere below as
`<user>`. Modern Raspberry Pi OS no longer defaults to the `pi` user, so don't
assume it; use whatever name you set here.
Boot the Pi, then open a terminal on it (directly, or over SSH: - **Set a username and password**, and
`ssh <user>@<pi-address>`). Install git and download this project: - **Enable SSH** (so you can connect to the Pi from another computer).
**Write down the username you choose** — you'll use it all over this guide, shown as
`<user>`. Modern Raspberry Pi OS no longer uses `pi` as the default username, so use
whatever you set here.
Boot the Pi, then open a terminal on it — either directly with a keyboard and
monitor, or from another computer over SSH:
```bash
ssh <user>@<pi-address>
```
Now install git and download this project:
```bash ```bash
sudo apt-get update sudo apt-get update
@ -56,120 +95,143 @@ git clone https://github.com/GRMrGecko/lutron-dmx-control.git
cd lutron-dmx-control cd lutron-dmx-control
``` ```
All the commands below are run from inside this `lutron-dmx-control` directory. Every command from here on is run from inside this `lutron-dmx-control` folder.
> Throughout this guide, replace `<user>` with the username you created above. If > Throughout this guide, replace `<user>` with the username you created above. For
> that username is **not** `pi`, you must also pass it to the installer > example, `lutron-dmx-control@<user>` becomes `lutron-dmx-control@john` if your
> (`TARGET_USER=<user>`, shown in step 2) and substitute it in every > username is `john`.
> `systemctl`/`journalctl` command (e.g. `lutron-dmx-control@<user>`, not `@pi`).
## 1. Install OLA (only if using DMX) ## 1. Install OLA (only if you want DMX / lighting-board control)
If you set `dmx.enabled: false`, skip this step — OLA does not need to be installed. **Not using a lighting board?** Skip this step entirely. Set `dmx.enabled: false` in
the config (step 3) and OLA never has to be installed.
Otherwise build and install OLA (the daemon plus the Python client bindings the Otherwise, build and install OLA (the background service plus the Python add-on this
control script uses). On a single-core Pi Zero this takes roughly 12 hours; the program uses):
script adds temporary swap on low-memory boards so the compile does not run out of
memory.
```bash ```bash
bash ./install-ola.sh bash ./install-ola.sh
``` ```
This installs the build dependencies, clones OLA at the `0.10.9` tag, and builds and This installs the build tools, downloads OLA at the tested `0.10.9` version, and
installs `olad` plus the `ola.ClientWrapper` Python module. Override the version or compiles and installs it. On a single-core Pi Zero the compile takes roughly **12
build directory with `OLA_VERSION=` / `BUILD_DIR=` if needed. hours** — that's normal. The script temporarily adds extra memory (swap) on
low-memory boards so the build doesn't run out of memory partway through.
## 2. Install the control service If you ever need to, you can override the version or build location with the
`OLA_VERSION=` / `BUILD_DIR=` environment variables.
`install.sh` installs the Python dependencies, the control script, the config file ## 2. Install the control program
and the `olad@<user>` / `lutron-dmx-control@<user>` systemd services. By default it
installs for the `pi` user; pass `TARGET_USER=<name>` for a different user. Run the installer:
```bash ```bash
sudo bash ./install.sh ./install.sh
# or, for a non-pi user:
sudo TARGET_USER=james bash ./install.sh
``` ```
The service is **enabled** (starts on boot) but, on a first install, is **not It installs the Python requirements, the control program itself, the config file,
started immediately** — the freshly installed config still has placeholder values. and the background services that keep everything running and start it on boot.
The installer prints the exact edit-then-start steps; see step 3 below. On a re-run
with an existing config it restarts the service to pick up the new version.
> Note: the systemd unit runs `/home/<user>/lutron-dmx-control.py`, so `<user>`'s On a **first install** the service is set to start on boot but is **not started
> home must be `/home/<user>`. If it lives elsewhere, the installer warns you to yet** — the config still has placeholder values you need to fill in. The installer
> adjust `ExecStart` in `lutron-dmx-control@.service`. prints the exact "edit, then start" steps for you (covered in step 3). If you run
`install.sh` again later (to update), it restarts the service to pick up the new
version.
## 3. Configure ## 3. Fill in the config
Edit `/etc/lutron-dmx-control/config.yaml` (installed from `config.example.yaml`) and set: Open the config file in a text editor (`nano` is beginner-friendly):
- `serial.device` — your serial device (use `ls -lah /dev/serial/by-id/`). ```bash
- `qse.integration_id` and `qse.zones` — to match your GRAFIK Eye unit. sudo nano /etc/lutron-dmx-control/config.yaml
- `dmx.enabled` / `dmx.universe` / `dmx.start_address` — for your DMX layout. ```
`dmx.lockout_sec` (default `5`) sets how long an active DMX signal locks out MQTT
control. Set `dmx.enabled: false` to run without OLA/DMX.
- `mqtt.broker`, `mqtt.username`, `mqtt.password` — if using MQTT. Set
`mqtt.enabled: false` to run without MQTT/Home Assistant; `paho-mqtt` is then not
required.
The config is searched for at `--config PATH`, then `$LUTRON_CONFIG`, then `config.yaml` This file was created from `config.example.yaml` and is heavily commented, so each
next to the script, then `~/.config/lutron-dmx-control/config.yaml`, then setting explains itself. The important ones:
`/etc/lutron-dmx-control/config.yaml`. It holds the MQTT password, so it is `chmod 600`
and excluded from git (`config.yaml` in `.gitignore`); only `config.example.yaml` is
committed.
Then start (first install) or restart (after edits) the service: - **`serial.device`** — which USB-serial adapter to use. Find yours by running
`sudo systemctl start lutron-dmx-control@pi` (use `restart` if it is already running). `ls -lah /dev/serial/by-id/` and copying the matching path.
Check it came up cleanly with `journalctl -u lutron-dmx-control@pi -f`. - **`qse.integration_id` and `qse.zones`** — set these to match your GRAFIK Eye
(the integration ID is assigned in Lutron's programming; zones is how many
dimmable zones your model has).
- **`dmx.*`** — your DMX layout (`universe`, `start_address`). `dmx.lockout_sec`
(default `5`) is how long an active DMX signal keeps Home Assistant from changing
the lights, so the lighting board stays in charge during a show. Set
`dmx.enabled: false` to run without DMX/OLA.
- **`mqtt.*`** — your MQTT broker address and `username`/`password` for Home
Assistant. Set `mqtt.enabled: false` to run without MQTT (then `paho-mqtt` isn't
needed).
Save and exit (`Ctrl+O`, `Enter`, then `Ctrl+X` in nano).
> **Where the config lives:** the program looks for it in this order — `--config
> PATH`, then `$LUTRON_CONFIG`, then a `config.yaml` next to the program, then
> `~/.config/lutron-dmx-control/config.yaml`, then
> `/etc/lutron-dmx-control/config.yaml` (where the installer puts it). Because it
> holds your MQTT password, it's locked down (`chmod 600`) and kept out of git; only
> the `config.example.yaml` template is committed.
Now start the service (use `restart` instead of `start` if it's already running, e.g.
after editing the config). **Remember to replace `<user>` with your username:**
```bash
sudo systemctl start lutron-dmx-control@<user>
```
Check that it started cleanly (press `Ctrl+C` to stop watching the log):
```bash
journalctl -u lutron-dmx-control@<user> -f
```
# OLA / DMX configuration # OLA / DMX configuration
`install.sh` configures OLA for **network DMX only (E1.31/sACN)** by default: it By default, `install.sh` sets OLA up for **network DMX only (E1.31/sACN)**: it turns
disables every OLA plugin except `e131`. This matters because olad's serial/USB off every OLA plugin except `e131`. This matters because OLA's serial/USB plugins
device plugins (e.g. `usbserial`) otherwise auto-probe and grab the QSE's serial would otherwise grab your USB-serial adapter (`/dev/ttyUSB*`) out from under this
adapter (`/dev/ttyUSB*`), conflicting with this program. The plugin configs live in program. The plugin settings live in `~/.ola/` if you want to change them later.
`~/.ola/` if you want to change this later.
To enable a different/extra plugin, stop olad, flip its config, and restart: To turn on a different or extra plugin, stop OLA, change its setting, and start it
again (replace `<user>` with your username):
```bash ```bash
sudo systemctl stop olad@pi sudo systemctl stop olad@<user>
sed -i '/^enabled\s*=/c\enabled = true' ~/.ola/ola-artnet.conf # example: also accept Art-Net sed -i '/^enabled\s*=/c\enabled = true' ~/.ola/ola-artnet.conf # example: also accept Art-Net
sudo systemctl start olad@pi sudo systemctl start olad@<user>
``` ```
## Receiving sACN (patching the universe) ## Receiving sACN (patching the universe)
For olad to actually receive sACN, an **E1.31 input port must be patched to your OLA For OLA to actually *receive* sACN, an **E1.31 input port has to be "patched" to your
universe** — the OLA universe number is the sACN universe (e.g. universe `3` = OLA universe**. The OLA universe number is the same as the sACN universe (e.g.
multicast `239.255.0.3`). Registering the universe from the client is not enough; universe `3` = multicast address `239.255.0.3`). Just registering the universe isn't
without a patched input port olad never joins the sACN multicast group. enough — without a patched input port, OLA never joins the network group that carries
the DMX data.
`install.sh` does this automatically: it patches the E1.31 input port to the `install.sh` does this for you automatically, patching the E1.31 input port to the
`dmx.universe` from your `config.yaml`. To do it (or change it) by hand: `dmx.universe` from your `config.yaml`. To do it (or change it) by hand:
```bash ```bash
# Find the E1.31 device id, then patch input port 0 to your universe (here 3): # Find the E1.31 device id, then patch input port 0 to your universe (here 3):
ola_dev_info ola_dev_info
ola_patch --device 1 --port 0 --input --universe 3 ola_patch --device 1 --port 0 --input --universe 3
# Confirm the multicast join on your active interface (eth0 wired, wlan0 on a Pi Zero W): # Confirm it joined the multicast group on your network interface
# (eth0 = wired, wlan0 = Wi-Fi on a Pi Zero W):
ip maddr show dev eth0 | grep 239.255.0.3 ip maddr show dev eth0 | grep 239.255.0.3
curl -s http://localhost:9090/get_dmx?u=3 # confirm DMX values are arriving curl -s http://localhost:9090/get_dmx?u=3 # confirm DMX values are arriving
``` ```
You can also patch from the olad web UI at the Pi's IP, port `9090`. The patch is You can also do this from OLA's web page — browse to the Pi's IP address on port
saved in `~/.ola/` and survives restarts/reboots. `9090`. The patch is saved in `~/.ola/` and survives restarts and reboots.
> Note: on the console/desktop sending sACN, a "changes only" / "send on change" > **Tip:** on the console or software sending sACN, a "changes only" / "send on
> option means it only transmits when levels change. Prefer a continuous stream so > change" option means it only transmits when levels change. Prefer a continuous
> olad has data immediately after a restart. > stream so OLA has data to work with immediately after a restart.
# Home Assistant & MQTT (Docker) # Home Assistant & MQTT (Docker)
I run Home Assistant and the Mosquitto MQTT broker in Docker via `docker compose`. I run Home Assistant and the Mosquitto MQTT broker in Docker using `docker compose`.
A minimal `compose.yaml`: Here's a minimal `compose.yaml`:
```yaml ```yaml
services: services:
@ -191,12 +253,14 @@ services:
network_mode: host network_mode: host
``` ```
`network_mode: host` lets Home Assistant discover the broker and the control script `network_mode: host` lets Home Assistant find the broker and lets this control
publish to it on `127.0.0.1:1883`. Bring it up with `docker compose up -d`. program publish to it at `127.0.0.1:1883`. Start it all with `docker compose up -d`.
## Mosquitto config ## Mosquitto config
Mosquitto needs a config and a password in the mounted `./mosquitto` directory. Mosquitto (the MQTT broker) needs a config file and a password in the mounted
`./mosquitto` folder.
`./mosquitto/mosquitto.conf`: `./mosquitto/mosquitto.conf`:
``` ```
@ -208,15 +272,15 @@ password_file /mosquitto/config/pwfile
acl_file /mosquitto/config/aclfile acl_file /mosquitto/config/aclfile
``` ```
`./mosquitto/aclfile` (grant the `mqtt` user full access): `./mosquitto/aclfile` (gives the `mqtt` user full access):
``` ```
user mqtt user mqtt
topic readwrite # topic readwrite #
``` ```
Create the password file (use the same `mqtt` user/password you put in Create the password file — use the **same `mqtt` user and password you put in
`config.yaml`): `config.yaml`**:
```bash ```bash
docker compose run --rm mqtt mosquitto_passwd -c -b /mosquitto/config/pwfile mqtt 'your-password' docker compose run --rm mqtt mosquitto_passwd -c -b /mosquitto/config/pwfile mqtt 'your-password'
@ -225,12 +289,14 @@ docker compose restart mqtt
## Home Assistant integration ## Home Assistant integration
In Home Assistant, add the **MQTT** integration (Settings → Devices & Services) and In Home Assistant, add the **MQTT** integration (**Settings → Devices & Services**)
point it at the broker (host `127.0.0.1`, port `1883`, the `mqtt` user/password). and point it at the broker: host `127.0.0.1`, port `1883`, and the `mqtt`
user/password.
With `mqtt.discovery: true` (the default in `config.yaml`), the light is published via With `mqtt.discovery: true` (the default in `config.yaml`), the light is announced to
Home Assistant MQTT discovery and appears automatically — no YAML needed. To disable Home Assistant automatically and shows up on its own — no YAML editing required. If
discovery, set `mqtt.discovery: false` and add the light manually: you'd rather add it manually, set `mqtt.discovery: false` and add this to your Home
Assistant config:
```yaml ```yaml
light: light:
@ -244,18 +310,19 @@ light:
supported_color_modes: ["brightness"] supported_color_modes: ["brightness"]
``` ```
# Recommended: watchdog # Recommended: hardware watchdog
Enable the hardware watchdog on the Pi to auto-reboot on a system crash. The Raspberry Pi has a built-in hardware watchdog that can automatically reboot the
Pi if it ever locks up. It's worth enabling for an always-on device like this.
Add to `/boot/firmware/config.txt` (or `/boot/config.txt` on older images) under the Add this to `/boot/firmware/config.txt` (or `/boot/config.txt` on older images) under
`[all]` section: the `[all]` section:
``` ```
watchdog=on watchdog=on
``` ```
Uncomment `RuntimeWatchdogSec` in `/etc/systemd/system.conf` and set it: Then uncomment `RuntimeWatchdogSec` in `/etc/systemd/system.conf` and set it:
``` ```
RuntimeWatchdogSec=10s RuntimeWatchdogSec=10s

View file

@ -2,17 +2,41 @@
set -e set -e
USER=$(whoami) # Determine the service user before we (potentially) re-exec as root, so we can
if [ "$USER" != "root" ]; then # capture the human who invoked the script. Priority: explicit TARGET_USER env,
echo "Please use sudo with this install script to ensure right permissions for installation." # then $SUDO_USER (set when already run via sudo), then the current user. We fall
# back to "pi" only if we somehow end up with an empty or root value.
DEFAULTED_TO_PI=0
if [ -z "$TARGET_USER" ]; then
TARGET_USER="${SUDO_USER:-$(whoami)}"
fi
if [ -z "$TARGET_USER" ] || [ "$TARGET_USER" = "root" ]; then
TARGET_USER="pi"
DEFAULTED_TO_PI=1
fi
# Re-run ourselves under sudo if we are not already root. Pass TARGET_USER through
# so the resolved (non-root) user survives the privilege escalation.
if [ "$(id -u)" -ne 0 ]; then
if command -v sudo >/dev/null 2>&1; then
exec sudo TARGET_USER="$TARGET_USER" "$0" "$@"
fi
echo "Please run this install script as root (or install sudo) to ensure right permissions for installation."
exit 1 exit 1
fi fi
# Service user (matches the systemd template instance: lutron-dmx-control@<TARGET_USER>). # Service user (matches the systemd template instance: lutron-dmx-control@<TARGET_USER>).
TARGET_USER="${TARGET_USER:-pi}"
TARGET_HOME=$(getent passwd "$TARGET_USER" | cut -d: -f6) TARGET_HOME=$(getent passwd "$TARGET_USER" | cut -d: -f6)
# If we couldn't infer a user and the "pi" fallback doesn't exist on this system,
# ask which user the service should run as rather than failing outright.
if [ -z "$TARGET_HOME" ] && [ "$DEFAULTED_TO_PI" -eq 1 ]; then
read -r -p "Default user 'pi' not found. Which user should the service run as? " TARGET_USER
TARGET_HOME=$(getent passwd "$TARGET_USER" | cut -d: -f6)
fi
if [ -z "$TARGET_HOME" ]; then if [ -z "$TARGET_HOME" ]; then
echo "Target user '$TARGET_USER' does not exist. Re-run with TARGET_USER=<name> sudo ./install.sh" echo "Target user '$TARGET_USER' does not exist. Re-run with TARGET_USER=<name> ./install.sh"
exit 1 exit 1
fi fi