A macOS desktop app for controlling DMX lighting via an Enttec USB DMX Pro Mk2. Built for live environments — fast scene recall, DCA-style fixture groups, multi-channel fixtures with color mixing, and Bitfocus Companion integration.
macOS only. Windows is not supported — the Enttec driver relies on macOS serial port access.
git clone https://github.com/bhdoggett/lightz.git
cd lightz
npm install
npm run build:macOpen dist-electron/Lightz-0.2.9-arm64.dmg (Apple Silicon) or Lightz-0.2.9.dmg (Intel) from Finder. Drag Lightz to Applications.
Download the right file for your Mac:
| Download | For |
|---|---|
| Lightz-0.2.9-arm64.dmg | Apple Silicon (M1 and later) |
| Lightz-0.2.9.dmg | Intel Mac |
Double-click the .dmg, drag Lightz to Applications, then open it.
macOS Gatekeeper: Because Lightz is not notarized, macOS may block it on first launch.
- If you see "Open Anyway" in System Settings → Privacy & Security, click it.
- If you see "Lightz is damaged and can't be opened", run this in Terminal, then try again:
xattr -cr /Applications/Lightz.app
- Enttec USB DMX Pro Mk2 (dual-universe USB-to-DMX interface)
- macOS 12 Monterey or later (Apple Silicon and Intel supported)
No additional driver needed on macOS 12+. The Enttec Mk2 appears automatically in the Settings device list — no downloads required.
On older macOS versions, you may need to install the driver manually. Download it from the Enttec support page or the FTDI VCP driver page if the device is not detected after plugging in.
- Plug in the Enttec device via USB.
- Open Lightz.
- Click the Settings icon (top right).
- Under DMX Device, click ↺ Refresh — your device should appear as
/dev/cu.usbmodem.... - Click the device path to select it, then click Connect.
- The connection badge in the header turns green: DMX Connected.
Both DMX universes are displayed as stacked, collapsible sections (Universe 1 above Universe 2). Use this for initial patching and diagnosing which channel controls which fixture. Channel labels show universe-prefixed numbers (e.g. 1-001, 2-001). Click a channel label to name it — named channels become fixtures in the Custom tab automatically.
Your labeled fixtures appear here, grouped by universe and collapsible. Use this during a service. Sections (Scenes, Groups, Fixtures) can be collapsed to reclaim screen space.
A scene is a snapshot of all fixture values at a moment in time.
- Save Scene — click + Save Scene to open a popover, type a name and optional fade duration, click Save.
- Activate — click a scene button to fade or snap all fixtures to those values.
- Edit — select a scene, click Edit to rename, change fade time, or delete.
- Drag to reorder — drag scene buttons left/right to rearrange.
Groups work like a soundboard DCA — a master control that scales a set of fixtures together.
- Create a group via + Group in the Custom tab.
- Each fixture can belong to only one group. The group color appears on the master fader fill and as an indicator dot on sub-faders.
- ○ (Full) — forces all fixtures in the group to 255 regardless of scene values.
- ✕ (Mute) — silences all fixtures in the group.
- Fader — scales output proportionally (50% = half brightness).
- Groups reset to 100% on every app launch — they are session controls, not saved state.
A 2D stage view that simulates your lighting output — works with or without DMX hardware connected.
- Sticky bottom panel — drag-resizable, collapsible, or pop out to a separate window.
- Stage layout — fixtures render as colored circles at their current intensity and color. Drag them to arrange a room layout.
- Grid editing — click the pencil icon to enter edit mode. Fixtures snap to grid points. Add/remove rows and columns with edge +/- buttons.
- Ghost-based drag — dragging a light shows a ghost circle with snap indicators at the target position. Invalid placements (collisions, out of bounds) show red indicators. Fixtures stay in place until you drop.
- Marquee selection — click and drag on empty stage space to draw a selection rectangle. Selected lights get an accent outline. Drag any selected light to move the whole group together, maintaining relative positions.
- Cmd+click — toggle individual lights in/out of the selection.
- Delete/Backspace — removes selected lights from the grid (returns them to unplaced).
- Fit/Scroll mode — toggle between stretching the grid to fit the window or using a fixed-proportion grid with zoom and scrolling. In scroll mode, grid cells are always square regardless of window size.
- Multiple lights per channel — one DMX channel often controls several physical fixtures. Duplicate a light with + to add more visual instances on the same channel.
- Unplaced fixtures — fixtures not yet positioned appear in a sidebar. Click to select, Shift-click for range, Cmd-click to toggle. Drag onto the grid — hold Shift during drag to place vertically instead of horizontally.
- Group overrides — mute, full, and fader states from groups are reflected in the visualizer.
- Labels toggle — click the tag icon to show/hide fixture names and intensity percentages. Hover any light to see its info.
- Pop-out window — detach the visualizer to a separate window (great for a second monitor), dock it back with one click.
✕ All Off is always visible in the tab bar. Clicking it zeros all 512 channels on both universes immediately and clears the active scene.
A show file captures your entire rig: fixture definitions, scenes, group setup, and device settings.
- Shows are saved to
~/Documents/Lightz/as named JSON files — no file picker needed. - Click Shows in the header to open, save, or manage shows.
- + New Show clears all fixtures, scenes, and groups (device settings preserved).
- Share show files by copying the
.jsonfiles from~/Documents/Lightz/.
From the Custom tab: Click + Add Channels → select channels by range (1-8, 1,3,5) or by clicking the channel grid → name each fixture → Add Fixtures.
From the Full tab: Click any channel label on a fader to type a name. Named channels become fixtures in the Custom tab automatically.
Click + Add Custom Fixture to open the fixture builder:
- Name the fixture (becomes the master fader label).
- Set the Starting Channel and Universe.
- Add channels one by one — each channel has a role (Red, Green, Blue, Amber, White, UV, Dimmer, Strobe, Other) and a label.
- Link toggles whether a channel follows the master fader. Linked channels scale together; unlinked channels are independent.
- Save as Template to reuse this fixture layout for other fixtures of the same type.
Multi-channel fixtures appear in the Custom tab as a fader card with:
- Master fader — scales all linked sub-channels proportionally.
- Color swatch / picker — click to open an HSV color picker that drives the RGB, Amber channels together. White and UV channels are left untouched by the color picker and are controlled independently via their sub-faders.
- ⚙ Gear button — reopens the fixture editor to rename, add/remove channels, or change roles.
- Expand (›) — reveals all individual sub-faders with role-colored fills (red, green, blue, etc.) and link toggles beneath each.
- Link toggles — click the chain icon below a sub-fader to unlink it from the master (e.g. to lock a strobe channel independently).
- RGB/RGBW: The color picker controls Red, Green, Blue, and derives Amber from warm tones automatically.
- White channel: Lightens the displayed color toward white when raised — controlled only via its sub-fader, not the picker.
- UV channel: Contributes to the color display but is not set by the color picker — control it via its sub-fader.
| Setting | Description |
|---|---|
| DMX Device | Auto-detects USB serial devices. Click Refresh, then select your Enttec path. |
| Companion HTTP Port | Default: 5551. Change only if another service uses that port. Restart required. |
Lightz runs a local HTTP server that Companion can call to fire scenes.
| Method | Endpoint | Description |
|---|---|---|
GET |
/status |
Health check → { status: "ok" } |
GET |
/scenes |
List all scenes → [{ id, name }] |
POST |
/scenes/:id/activate |
Activate a scene by slug ID |
- Add a button → action type: Generic HTTP
- Method:
POST - URL:
http://localhost:5551/scenes/<scene-slug>/activate - The scene slug is shown in the Settings modal next to each scene name.
| Action | How |
|---|---|
| Type a channel value | Click the value number above any fader to type a DMX value (0–255) |
| Rename a fixture | Click the channel label or fixture name on any fader |
| Clear a fixture name | Rename to empty — removes the fixture |
| Reorder scenes | Drag a scene button left or right |
| Fire a scene | Click the scene button |
| All Off (kill switch) | Click ✕ All Off in the tab bar — zeros all channels on both universes |
| Open color picker | Click the color swatch on a multi-channel fixture master fader |
| Edit a custom fixture | Click the ⚙ gear icon on the fixture master fader |
| Expand sub-faders | Click the › arrow on a multi-channel fixture |
| Link/unlink a sub-fader | Click the chain icon beneath the sub-fader label |
| Collapse a section | Click the section header (Scenes, Groups, Universe 1, Universe 2, etc.) |
| Marquee-select lights | Click and drag on empty stage space in edit mode |
| Toggle light selection | Cmd+click a light on the visualizer stage |
| Move selected lights | Drag any selected light — group moves together |
| Unplace selected lights | Press Delete or Backspace with lights selected |
| Multi-select unplaced | Shift-click (range) or Cmd-click (toggle) in unplaced sidebar |
| Place vertically | Hold Shift while dragging unplaced fixtures onto the grid |
The Mk2 exposes two independent DMX output ports. Universe 1 is routed to Port 1 and Universe 2 to Port 2 automatically — no configuration needed.
The Mk2 uses an undocumented binary protocol on top of standard serial (250000 baud, 8N2). Two initialization packets must be sent immediately after the serial port opens, or Port 2 will silently ignore all writes:
| Purpose | Packet (hex) |
|---|---|
| Enable API2 mode | 7E 0D 04 00 AD 88 D0 C8 E7 |
| Assign both ports as DMX outputs | 7E CB 02 00 01 01 E7 |
These packets are not in the official Enttec documentation. They were sourced from the QLC+ open-source codebase (enttecdmxusbpro.cpp).
Once initialized, each universe is sent as a standard Pro API packet on a dedicated interval:
| Universe | Port label | Interval |
|---|---|---|
| U1 | 0x06 |
every 30ms, starting immediately |
| U2 | 0xa9 |
every 30ms, starting 15ms after U1 |
The 15ms stagger prevents the two ~518-byte packets from colliding in the serial write buffer. At 250kbaud each packet takes ~20.7ms to clock out, so back-to-back sends in the same tick cause the second packet to be silently dropped.
- Node.js 20+
- Xcode Command Line Tools —
xcode-select --install(required for native serialport)
npm install # install dependencies (also runs electron-rebuild)
npm run dev # launch Electron in development mode
npm run dev:web # launch web version in browser (no Electron)
npm test # run test suite
npm run build:mac # build distributable .dmg files
npm run build:web # build static web SPA to dist-web/Output DMGs: dist-electron/Lightz-<version>[-arm64].dmg
electron/
main/index.ts Electron entry, BrowserWindow, IPC wiring
preload/index.ts contextBridge — typed window.electronAPI
dmx.ts DmxManager — hardware + group multipliers
server.ts Express — Companion HTTP server
store.ts electron-store — config persistence
ipc.ts ipcMain handlers
shows-library.ts Named show file read/write
slug.ts Scene ID slug generation
src/
shared/
types.ts Domain types — Fixture, Scene, Group, Config
dmx-utils.ts Shared interpolation and clamping math
electron-api.d.ts Window.electronAPI type declarations
renderer/
App.tsx Root — header, show management, modal routing
main.tsx Electron entry — wraps App in ElectronApiProvider
main-web.tsx Web entry — wraps App in WebApiProvider with demo config
globals.css CSS custom properties (design tokens)
api/
types.ts LightzApi interface — platform-agnostic API surface
context.tsx ApiContext + useApi() hook
electron-provider Wraps window.electronAPI for Electron
web-provider In-memory store with demo config for web
demo-config Pre-loaded demo show data
components/ Folder-per-component (ComponentName/index.ts barrel)
LightVisualizer 2D stage visualizer with draggable lights, grid editor
PopoutWindow Renders children in a separate browser window
RawFader Base fader (value, slider, toggle, label)
MultiFixtureFader Multi-channel fixture card with color picker
ScenesStrip Scene buttons row with save/edit popover
GroupFader DCA-style group master with ○/✕ overrides
GroupStrip Horizontal group rack
... (19 components total)
views/
MainView Tab bar, section layout, fixture/scene/group wiring
LiveView Full raw-channel view (both universes, collapsible)
hooks/
useDmxState In-memory DMX channel state
utils/
colorSync RGB/RGBW color math
gangFader Proportional ratio math for linked channel scaling
vite.web.config.ts Standalone Vite config for web build
Dockerfile Multi-stage build for Coolify deployment
- Electron 28 + electron-vite 2
- React 18 + TypeScript + CSS Modules
- react-colorful for HSV color picker
- serialport for Enttec hardware communication
- electron-store for config persistence
- Express for Companion HTTP server
- Vitest + React Testing Library