From b9fa97faa9b1547892e25e190716d8e56611c754 Mon Sep 17 00:00:00 2001 From: James Coleman Date: Wed, 3 Jun 2026 20:41:51 -0500 Subject: [PATCH] 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. --- README.md | 307 ++++++++++++++++++++++++++++++++--------------------- install.sh | 34 +++++- 2 files changed, 216 insertions(+), 125 deletions(-) diff --git a/README.md b/README.md index 4f2856a..cd6a4ad 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,92 @@ -This project controls the GRAFIK Eye QS control panel via a QSE-CI-NWK-E over its -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. +# Lutron GRAFIK Eye QS — DMX & Home Assistant control -I run this on a Raspberry Pi (a Pi Zero works) on **Raspberry Pi OS / Raspbian 13 -(Trixie)**. OLA is no longer packaged for recent Debian/Raspbian releases, so it is -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. +This lets you control a **Lutron GRAFIK Eye QS** lighting unit from two places it +normally can't be reached from: -DMX and MQTT are independent, optional components. Serial control of the QSE is -always active; you can run with DMX only, MQTT only, or both. +- **A lighting board / theatrical software** (so the GRAFIK Eye's zones can be run + 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 -- A **Raspberry Pi** running Raspberry Pi OS (a Pi Zero is enough; a Pi Zero **W** - or any model with networking is needed for sACN/MQTT). These instructions assume - **Raspberry Pi OS / Raspbian 13 (Trixie)**. -- A **USB-to-serial adapter** wired to the QSE-CI-NWK-E's serial terminals (the - config example uses a Prolific PL2303-style adapter; any 3.3 V / RS-232 adapter - that matches your wiring works). -- A **GRAFIK Eye QS** with a QSE-CI-NWK-E network/serial interface. -- For DMX: a lighting console or software sending **sACN/E1.31** on your network. -- For MQTT / Home Assistant: a running **MQTT broker** (the Docker setup below - includes one). +- A **Raspberry Pi** running Raspberry Pi OS. A Pi Zero is enough for serial-only + control; for sACN or Home Assistant you need networking, so use a Pi Zero **W** + (Wi-Fi) or any networked model. These instructions assume **Raspberry Pi OS / + Raspbian 13 (Trixie)**. +- A **USB-to-serial adapter** wired to the QSE-CI-NWK-E's serial terminals. The + example config uses a common Prolific PL2303-style adapter; any 3.3 V / RS-232 + adapter that matches your wiring will do. +- A **GRAFIK Eye QS** with the **QSE-CI-NWK-E** network/serial interface module. +- **For lighting-board control:** a lighting console or software that sends + **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. -2. **Install OLA** (step 1) — only if you use DMX. This is the slow part (~1–2 h on a Pi Zero). -3. **Install the control service** (step 2) and **configure it** (step 3). +1. **Get the Pi ready and download the code** (step 0). +2. **Install OLA** (step 1) — *only if you want lighting-board/DMX control.* This is + the slow part (~1–2 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 -## 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 -[Raspberry Pi Imager](https://www.raspberrypi.com/software/). In the imager's -settings (the gear / "Edit settings"), **set a username and password and enable -SSH** — remember the username you choose; you'll use it everywhere below as -``. Modern Raspberry Pi OS no longer defaults to the `pi` user, so don't -assume it; use whatever name you set here. +If you're starting from a blank SD card, flash **Raspberry Pi OS** with the +[Raspberry Pi Imager](https://www.raspberrypi.com/software/). Before you write the +card, open the imager's settings (the **gear** / **"Edit settings"** button) and: -Boot the Pi, then open a terminal on it (directly, or over SSH: -`ssh @`). Install git and download this project: +- **Set a username and password**, and +- **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 +``. 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 @ +``` + +Now install git and download this project: ```bash sudo apt-get update @@ -56,120 +95,143 @@ git clone https://github.com/GRMrGecko/lutron-dmx-control.git 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 `` with the username you created above. If -> that username is **not** `pi`, you must also pass it to the installer -> (`TARGET_USER=`, shown in step 2) and substitute it in every -> `systemctl`/`journalctl` command (e.g. `lutron-dmx-control@`, not `@pi`). +> Throughout this guide, replace `` with the username you created above. For +> example, `lutron-dmx-control@` becomes `lutron-dmx-control@john` if your +> username is `john`. -## 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 -control script uses). On a single-core Pi Zero this takes roughly 1–2 hours; the -script adds temporary swap on low-memory boards so the compile does not run out of -memory. +Otherwise, build and install OLA (the background service plus the Python add-on this +program uses): ```bash bash ./install-ola.sh ``` -This installs the build dependencies, clones OLA at the `0.10.9` tag, and builds and -installs `olad` plus the `ola.ClientWrapper` Python module. Override the version or -build directory with `OLA_VERSION=` / `BUILD_DIR=` if needed. +This installs the build tools, downloads OLA at the tested `0.10.9` version, and +compiles and installs it. On a single-core Pi Zero the compile takes roughly **1–2 +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 -and the `olad@` / `lutron-dmx-control@` systemd services. By default it -installs for the `pi` user; pass `TARGET_USER=` for a different user. +## 2. Install the control program + +Run the installer: ```bash -sudo bash ./install.sh -# or, for a non-pi user: -sudo TARGET_USER=james bash ./install.sh +./install.sh ``` -The service is **enabled** (starts on boot) but, on a first install, is **not -started immediately** — the freshly installed config still has placeholder values. -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. +It installs the Python requirements, the control program itself, the config file, +and the background services that keep everything running and start it on boot. -> Note: the systemd unit runs `/home//lutron-dmx-control.py`, so ``'s -> home must be `/home/`. If it lives elsewhere, the installer warns you to -> adjust `ExecStart` in `lutron-dmx-control@.service`. +On a **first install** the service is set to start on boot but is **not started +yet** — the config still has placeholder values you need to fill in. The installer +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/`). -- `qse.integration_id` and `qse.zones` — to match your GRAFIK Eye unit. -- `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. +```bash +sudo nano /etc/lutron-dmx-control/config.yaml +``` -The config is searched for at `--config PATH`, then `$LUTRON_CONFIG`, then `config.yaml` -next to the script, then `~/.config/lutron-dmx-control/config.yaml`, then -`/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. +This file was created from `config.example.yaml` and is heavily commented, so each +setting explains itself. The important ones: -Then start (first install) or restart (after edits) the service: -`sudo systemctl start lutron-dmx-control@pi` (use `restart` if it is already running). -Check it came up cleanly with `journalctl -u lutron-dmx-control@pi -f`. +- **`serial.device`** — which USB-serial adapter to use. Find yours by running + `ls -lah /dev/serial/by-id/` and copying the matching path. +- **`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 `` with your username:** + +```bash +sudo systemctl start lutron-dmx-control@ +``` + +Check that it started cleanly (press `Ctrl+C` to stop watching the log): + +```bash +journalctl -u lutron-dmx-control@ -f +``` # OLA / DMX configuration -`install.sh` configures OLA for **network DMX only (E1.31/sACN)** by default: it -disables every OLA plugin except `e131`. This matters because olad's serial/USB -device plugins (e.g. `usbserial`) otherwise auto-probe and grab the QSE's serial -adapter (`/dev/ttyUSB*`), conflicting with this program. The plugin configs live in -`~/.ola/` if you want to change this later. +By default, `install.sh` sets OLA up for **network DMX only (E1.31/sACN)**: it turns +off every OLA plugin except `e131`. This matters because OLA's serial/USB plugins +would otherwise grab your USB-serial adapter (`/dev/ttyUSB*`) out from under this +program. The plugin settings live in `~/.ola/` if you want to change them 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 `` with your username): ```bash -sudo systemctl stop olad@pi +sudo systemctl stop olad@ 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@ ``` ## Receiving sACN (patching the universe) -For olad to actually receive sACN, an **E1.31 input port must be patched to your OLA -universe** — the OLA universe number is the sACN universe (e.g. universe `3` = -multicast `239.255.0.3`). Registering the universe from the client is not enough; -without a patched input port olad never joins the sACN multicast group. +For OLA to actually *receive* sACN, an **E1.31 input port has to be "patched" to your +OLA universe**. The OLA universe number is the same as the sACN universe (e.g. +universe `3` = multicast address `239.255.0.3`). Just registering the universe isn't +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: ```bash # Find the E1.31 device id, then patch input port 0 to your universe (here 3): ola_dev_info 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 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 -saved in `~/.ola/` and survives restarts/reboots. +You can also do this from OLA's web page — browse to the Pi's IP address on port +`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" -> option means it only transmits when levels change. Prefer a continuous stream so -> olad has data immediately after a restart. +> **Tip:** on the console or software sending sACN, a "changes only" / "send on +> change" option means it only transmits when levels change. Prefer a continuous +> stream so OLA has data to work with immediately after a restart. # Home Assistant & MQTT (Docker) -I run Home Assistant and the Mosquitto MQTT broker in Docker via `docker compose`. -A minimal `compose.yaml`: +I run Home Assistant and the Mosquitto MQTT broker in Docker using `docker compose`. +Here's a minimal `compose.yaml`: ```yaml services: @@ -191,12 +253,14 @@ services: network_mode: host ``` -`network_mode: host` lets Home Assistant discover the broker and the control script -publish to it on `127.0.0.1:1883`. Bring it up with `docker compose up -d`. +`network_mode: host` lets Home Assistant find the broker and lets this control +program publish to it at `127.0.0.1:1883`. Start it all with `docker compose up -d`. ## 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`: ``` @@ -208,15 +272,15 @@ password_file /mosquitto/config/pwfile acl_file /mosquitto/config/aclfile ``` -`./mosquitto/aclfile` (grant the `mqtt` user full access): +`./mosquitto/aclfile` (gives the `mqtt` user full access): ``` user mqtt topic readwrite # ``` -Create the password file (use the same `mqtt` user/password you put in -`config.yaml`): +Create the password file — use the **same `mqtt` user and password you put in +`config.yaml`**: ```bash 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 -In Home Assistant, add the **MQTT** integration (Settings → Devices & Services) and -point it at the broker (host `127.0.0.1`, port `1883`, the `mqtt` user/password). +In Home Assistant, add the **MQTT** integration (**Settings → Devices & Services**) +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 -Home Assistant MQTT discovery and appears automatically — no YAML needed. To disable -discovery, set `mqtt.discovery: false` and add the light manually: +With `mqtt.discovery: true` (the default in `config.yaml`), the light is announced to +Home Assistant automatically and shows up on its own — no YAML editing required. If +you'd rather add it manually, set `mqtt.discovery: false` and add this to your Home +Assistant config: ```yaml light: @@ -244,18 +310,19 @@ light: 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 -`[all]` section: +Add this to `/boot/firmware/config.txt` (or `/boot/config.txt` on older images) under +the `[all]` section: ``` 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 diff --git a/install.sh b/install.sh index d598053..f04f5ec 100644 --- a/install.sh +++ b/install.sh @@ -2,17 +2,41 @@ set -e -USER=$(whoami) -if [ "$USER" != "root" ]; then - echo "Please use sudo with this install script to ensure right permissions for installation." +# Determine the service user before we (potentially) re-exec as root, so we can +# capture the human who invoked the script. Priority: explicit TARGET_USER env, +# 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 fi # Service user (matches the systemd template instance: lutron-dmx-control@). -TARGET_USER="${TARGET_USER:-pi}" 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 - echo "Target user '$TARGET_USER' does not exist. Re-run with TARGET_USER= sudo ./install.sh" + echo "Target user '$TARGET_USER' does not exist. Re-run with TARGET_USER= ./install.sh" exit 1 fi