Skip to content

tombueng/LumiGate

Repository files navigation

LumiGate — open-source Art-Net / sACN to DMX512 gateway with a live diagnostic web UI

LumiGate logo

LumiGate

Open-source Art-Net / sACN (E1.31) → DMX512 gateway for ESP32 / ESP32-S3 / Ethernet.

Not just another Art-Net node — a network DMX node and a live diagnostic tool.
Watch all 512 channels update in real time in your browser, get warned the instant two consoles fight over a universe, see per-sender FPS and frame jitter, and drive galvanically-isolated DMX out. Builds for a few dollars.

Flash in browser Build Latest firmware License: MIT

⚡ Flash from your browser  ·  ▶ Watch the demo  ·  🛠 Custom PCB  ·  Install

Status page Settings page
Status page Settings page

Web UI walkthrough

A guided tour of every control — manual channel control, labels, sparkline history, identify, blackout, and all settings:

LumiGate web UI walkthrough

Watch the full walkthrough on YouTube  ·  regenerate with docs/screenshot.mjs


Features

Feature Details
Art-Net → DMX512 Full 512-channel, unicast or broadcast, universe configurable (0–15)
sACN / E1.31 → DMX512 Multicast receive, universe configurable, runs alongside Art-Net
Protocol selection Art-Net only / sACN only / Both — configurable in web UI
Live Web UI Bootstrap 5 dark theme, WebSocket push (~10/s), all 512 channels visible
Sender list Shows all active Art-Net / sACN senders with per-sender FPS
Conflict detection Warning banner when multiple senders are active simultaneously
Source merging Per-output HTP (highest takes precedence) / LTP (latest wins) / off, honouring the sACN priority field
Jitter stat Real-time inter-frame timing deviation (EMA)
Change log Live log of DMX value changes with top-N changed channels per frame
Sparkline Per-channel history sparkline in the channel detail modal
Channel labels Name any channel (e.g. "Front Wash L") — shown in grid, modal, and change log
Identify Flash a channel to full for ~1.5 s to physically locate the fixture
Manual DMX control Click any channel in browser, set value via slider
Blackout button Zero all channels instantly from browser
Art-Net / Manual toggle Switch between protocol passthrough and manual override
Failsafe output Continuous 40 Hz DMX refresh holds the last frame through brief input gaps
Static IP or DHCP Configurable static IP/gateway/subnet/DNS, or automatic DHCP
Mesh-aware WiFi Scans all channels and joins the strongest AP (multi-AP/mesh friendly)
WiFi Config Portal First-boot AP + captive portal via WiFiManager
Selectable network mode Pick the interface (WiFi or wired Ethernet) and WiFi mode (client/STA or standalone AP) in the web UI, on boards that have both
Standalone AP mode LumiGate hosts its own WiFi network so a phone/tablet/console connects directly and sends Art-Net with no router (reachable at 192.168.4.1)
Ethernet → AP fallback Auto-starts the WiFi AP if wired Ethernet is selected but the link is down, so the device stays reachable
Versioned OTA Pick & install any past release from a table, or auto-update to latest
OTA Updates ArduinoOTA (IDE/CLI) + manual .bin upload + one-click GitHub update
mDNS Reachable as dmx-gateway.local (hostname configurable)
REST API GET /dmx.json, /senders.json, /log.json, /version.json, /labels.json
Status LED Plain GPIO, WS2812 RGB NeoPixel, or 5-LED panel (v3) — codes network/idle/DMX/conflict/identify state
Up to 2 DMX outputs Two independent universes, each its own UART + RS485 transceiver (same universe on both = splitter)
Status display Optional I²C OLED (SSD1306 / SH1106) or colour SPI OLED (SSD1351) — IP, universe, FPS, sources + auto-rotating conflict/identify/manual banners
Configurable DMX pins Per output: universe, UART port, TX / RX / RTS GPIO — set at runtime via web UI, no recompile
NVS persistence Universe, protocol, IP config, labels, hostname, OTA password, LED/DMX pin config survive reboots
Config reset Hold BOOT button 3 s on startup, or via /reset page
Ethernet support WT32-ETH01 (LAN8720) and v3 (W5500) run wired LAN or WiFi, switchable at runtime; plus any ESP32 / ESP32-S3 + an external W5500 module (enable & pin it in /config); DHCP or static
Dual/triple target Builds for ESP32 (WROOM-32), ESP32-S3 (DevKitC-1), WT32-ETH01

Hardware

🛠 Custom PCB — LumiGate v3

Want the real thing instead of breadboard + modules? There's a complete open-source 4-layer PCB: ESP32-S3 with both WiFi and wired Ethernet (W5500), galvanically-isolated DMX output, USB-C power + flashing, status LEDs, BOOT/RST buttons, and an optional OLED/TFT display header — all palm-sized and fabricable at JLCPCB for a few dollars.

LumiGate v3 custom PCB

→ Full design, component rationale, BOM, gerbers & JLCPCB fab guide: hardware/

The rest of this section covers the simpler breadboard / module build (an off-the-shelf ESP32 DevKit

  • an isolated RS485 module) — perfect for getting started without fabricating a board.

Bill of Materials — WiFi build (ESP32 / ESP32-S3)

# Component Description Link
1 ESP32 DevKit v1 ESP32-WROOM-32, 30-pin, any CH340/CP2102 variant Amazon.de search
2 Waveshare TTL to RS485 (C) Galvanically isolated RS485 transceiver, auto-direction Amazon.de – B0D4TZQYVG
3 XLR-5 female panel socket Standard DMX output connector (XLR-3 also works) Amazon.de search
4 Jumper wires / dupont cables Male–male for breadboard, or direct solder any
5 USB-A to Micro-USB cable Power + serial flash any

Bill of Materials — Ethernet build (WT32-ETH01)

# Component Description Link
1 WT32-ETH01 ESP32 + LAN8720 Ethernet PHY, built-in RJ45 Amazon.de search
2 Waveshare TTL to RS485 (C) Same as WiFi build Amazon.de – B0D4TZQYVG
3 XLR-5 female panel socket Same as WiFi build any
4 Ethernet cable Cat5e or better any
5 5V power supply (500 mA+) WT32-ETH01 has no USB port — use a 5V supply on the VIN pin any

The WT32-ETH01 has a built-in RJ45 jack and LAN8720 PHY soldered on-board. No additional Ethernet module needed.

Optional for enclosure:

  • Project box / DIN-rail enclosure
  • 120 Ω termination resistor across DMX A/B at the cable end (required for long runs)

The RS485 Module: Waveshare TTL to RS485 (C)

This module is the key interface between the ESP32's UART and the DMX RS485 bus.

Property Value
Product name Waveshare TTL to RS485 (C)
Isolation Galvanic, 2500 V RMS (SP3485 + isolation transformer)
Direction control Automatic — no DE/RE pin needed
TTL voltage 3.3 V or 5 V (VCC selects level)
Max baudrate ≥ 250 kbps (verified working at DMX rate)
Connector (TTL side) 6-pin 2.54 mm header: VCC / GND / RXD / TXD / (unused) / (unused)
Connector (RS485 side) Screw terminal: A / B / GND
Dimensions ~38 × 14 mm

Why galvanic isolation?
DMX fixtures are often powered from separate circuits or have ground loops. The isolated module prevents ground noise from corrupting the signal and protects the ESP32 from voltage spikes.

Why auto-direction?
Standard RS485 requires toggling a DE/RE pin to switch between transmit and receive. The Waveshare (C) variant handles this internally based on line activity — no GPIO needed, firmware is simpler. Trade-off: RDM (which requires half-duplex arbitration) is not supported by this module.


Pinout Reference

Waveshare TTL to RS485 (C) — TTL header (left side):

Pin 1  VCC   → ESP32 3.3V
Pin 2  GND   → ESP32 GND
Pin 3  RXD   → ESP32 GPIO17  (default TX pin — configurable in web /config)
Pin 4  TXD   → ESP32 GPIO16  (default RX pin — configurable in web /config)
Pin 5  —     (not connected)
Pin 6  —     (not connected)

Different hardware? If your RS485 module or ESP32 board uses different GPIO pins, change TX/RX under Settings → DMX Output Pins in the web UI. No recompile needed.

Waveshare TTL to RS485 (C) — RS485 screw terminal (right side):

A  →  DMX XLR pin 3  (Data+)
B  →  DMX XLR pin 2  (Data–)
GND→  DMX XLR pin 1  (Shield/Ground)  [optional but recommended]

Wiring Diagram

LumiGate Wiring Diagram

Connection table:

ESP32 pin Module pin XLR pin
GPIO17 (TX) RXD
GPIO16 (RX) TXD
3.3V VCC
GND GND
A (RS485+) Pin 3
B (RS485–) Pin 2
GND GND Pin 1 (optional)

WT32-ETH01 wiring (different pins — GPIO16 used by LAN8720)

WT32-ETH01 pin Module pin XLR pin
GPIO4 (TX) RXD
GPIO5 (RX) TXD
3.3V VCC
GND GND
A (RS485+) Pin 3
B (RS485–) Pin 2

Do not use GPIO16 or GPIO17 on the WT32-ETH01 — they are connected to the LAN8720 Ethernet PHY (power and RMII).


Assembly Guide

Step 1 — Prepare the ESP32

No soldering required if using a DevKit with pre-soldered headers. Place it on a breadboard or in your enclosure.

Step 2 — Connect ESP32 ↔ RS485 module

Using dupont jumper wires:

Color convention From To
Red ESP32 3.3V Module VCC
Black ESP32 GND Module GND
Yellow ESP32 GPIO17 (TX) Module RXD
Green ESP32 GPIO16 (RX) Module TXD

Use 3.3V, not 5V — the ESP32's GPIOs are not 5V tolerant.

Step 3 — Wire the XLR connector

The DMX standard uses XLR-5 (5-pin), but most DMX fixtures also accept XLR-3. Pin numbering is the same:

XLR Pin Signal RS485 module
1 Shield / GND Module GND (optional)
2 DMX Data– Module B
3 DMX Data+ Module A
4 (unused in DMX)
5 (unused in DMX)

Screw the wires into the RS485 module's terminal block firmly.

Step 4 — Termination resistor (for longer cable runs)

For DMX cable runs over ~10 m, solder a 120 Ω resistor between pins 2 and 3 (A and B) at the far end of the DMX chain (i.e., inside the last fixture's XLR input). Most professional fixtures include this internally.

Step 5 — Power

The ESP32 DevKit is powered via its Micro-USB port. Any 5V USB power supply works (500 mA is sufficient). The RS485 module draws its power from the ESP32's 3.3V rail.


Notes

  • Do not connect DE/RE — the Waveshare (C) handles direction automatically. Connecting them will break transmission.
  • Keep TTL wires short (< 20 cm). Long unshielded wires on the TTL side pick up noise; the RS485 side handles long distances natively.
  • XLR gender: Use a female XLR panel socket for the DMX output. DMX sources (transmitters) use female XLR; receivers use male XLR. LumiGate is a source.

Software Stack

Library Purpose
someweisguy/esp_dmx ^4.1 DMX512 transmit via UART
rstephan/ArtnetWifi ^1.5 Art-Net UDP receiver (port 6454)
tzapu/WiFiManager ^2.0 WiFi config portal
ESP32Async/ESPAsyncWebServer Non-blocking HTTP server + WebSocket (port 80)
ESP32Async/AsyncTCP Async TCP backend (runs networking off the main loop)
adafruit/Adafruit NeoPixel ^1.12 WS2812 RGB status LED support
ArduinoOTA OTA firmware updates
ESPmDNS mDNS (dmx-gateway.local)
Preferences NVS persistent config
(built-in UDP) sACN / E1.31 multicast receive (port 5568)

Flashing Pre-built Firmware

⚡ Flash from your browser — no install, no command line

Open the web flasher in desktop Chrome or Edge, plug in your board over USB, pick your model, and click flash. That's it — no Python, no esptool, no toolchain. It always installs the latest release.

The manual / scripted methods below still work if you prefer them, or for the WT32-ETH01 (which has no USB port).

No toolchain needed. GitHub CI builds the firmware on every push to master.

Download latest release — includes firmware.bin, bootloader.bin, partitions.bin, boot_app0.bin.

Boot mode (required for all methods)

You must manually enter download mode before flashing:

  1. Hold BOOT button
  2. Press and release EN (or RST) while keeping BOOT held
  3. Release BOOT — the chip is now in download mode
  4. Run the flash command

ESP32-S3 DevKitC-1: use the USB-UART port (labeled on the board), not the native USB port. The native USB port cannot be used with esptool.

Windows — one-liner (PowerShell)

Downloads and runs flash.ps1, which installs Python + esptool automatically:

Set-ExecutionPolicy -Scope Process Bypass; irm https://raw.githubusercontent.com/tombueng/LumiGate/master/flash.ps1 | iex

Or save and run it manually:

# Download
Invoke-WebRequest https://raw.githubusercontent.com/tombueng/LumiGate/master/flash.ps1 -OutFile flash.ps1
# Run
Set-ExecutionPolicy -Scope Process Bypass
.\flash.ps1

The script: installs Python 3 via winget if missing → installs esptool via pip → downloads the four firmware blobs from the latest GitHub release → lets you pick a COM port → guides you through boot mode → flashes.

macOS / Linux — ESP32 (WROOM-32)

pip install esptool

REPO=tombueng/LumiGate
for f in firmware.bin bootloader.bin partitions.bin boot_app0.bin; do
  curl -sL "$(curl -s https://api.github.com/repos/$REPO/releases/tags/latest \
    | python3 -c "import sys,json; assets=json.load(sys.stdin)['assets']; \
      print(next(a['browser_download_url'] for a in assets if a['name']=='$f'))")" -o $f
done

# Enter boot mode first (see above), then:
esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 460800 \
  --before default_reset --after hard_reset \
  write_flash -z --flash_mode dio --flash_freq 80m \
  0x1000 bootloader.bin 0x8000 partitions.bin \
  0xe000 boot_app0.bin 0x10000 firmware.bin

macOS / Linux — ESP32-S3 (DevKitC-1)

Important: bootloader goes at 0x0000 on S3 (not 0x1000 like the original ESP32).

pip install esptool

REPO=tombueng/LumiGate
for f in firmware-esp32s3.bin bootloader-esp32s3.bin partitions-esp32s3.bin boot_app0.bin; do
  curl -sL "$(curl -s https://api.github.com/repos/$REPO/releases/tags/latest \
    | python3 -c "import sys,json; assets=json.load(sys.stdin)['assets']; \
      print(next(a['browser_download_url'] for a in assets if a['name']=='$f'))")" -o $f
done

# Enter boot mode first, use the USB-UART port, then:
esptool.py --chip esp32s3 --port /dev/ttyUSB0 --baud 921600 \
  --before default_reset --after hard_reset \
  write_flash -z --flash_mode dio --flash_freq 80m \
  0x0000 bootloader-esp32s3.bin 0x8000 partitions-esp32s3.bin \
  0xe000 boot_app0.bin 0x10000 firmware-esp32s3.bin

Replace /dev/ttyUSB0 with your port (/dev/tty.usbserial-* on macOS).

macOS / Linux — WT32-ETH01

pip install esptool

REPO=tombueng/LumiGate
for f in firmware-wt32eth01.bin bootloader-wt32eth01.bin partitions-wt32eth01.bin boot_app0.bin; do
  curl -sL "$(curl -s https://api.github.com/repos/$REPO/releases/tags/latest \
    | python3 -c "import sys,json; assets=json.load(sys.stdin)['assets']; \
      print(next(a['browser_download_url'] for a in assets if a['name']=='$f'))")" -o $f
done

# Enter boot mode (hold BOOT + press RST), then:
esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 460800 \
  --before default_reset --after hard_reset \
  write_flash -z --flash_mode dio --flash_freq 80m \
  0x1000 bootloader-wt32eth01.bin 0x8000 partitions-wt32eth01.bin \
  0xe000 boot_app0.bin 0x10000 firmware-wt32eth01.bin

Build & Flash

Requirements

  • PlatformIO with VS Code extension
  • ESP32 connected via USB (CH340 or CP2102)

Project Structure

LumiGate/
├── src/
│   ├── main.cpp          ← firmware logic
│   ├── pages/            ← edit web UI here (plain HTML)
│   │   ├── index.html
│   │   ├── config.html
│   │   ├── config_saved.html
│   │   ├── reset.html
│   │   └── reset_done.html
│   ├── assets/           ← images served by the ESP32
│   │   └── logo.png      ← 96×96 px, replaces itself on rebuild
│   └── generated/        ← auto-created at build time, gitignored
├── docs/                 ← documentation assets (README images)
├── extra_scripts.py      ← PlatformIO pre-build hook
└── platformio.ini

How the build pipeline works

Before every pio run, PlatformIO executes extra_scripts.py, which:

  1. Reads every src/pages/*.html file
  2. Reads every src/assets/*.png file
  3. Converts them to C PROGMEM arrays / string literals and writes them to src/generated/*.h
  4. main.cpp #includes those headers — the HTML and images become part of the firmware binary

To change the web UI, edit the HTML files in src/pages/ and rebuild — no C++ changes needed.
To replace the logo, drop a new 96×96 PNG into src/assets/logo.png and rebuild.

Dynamic values (IP address, universe number, etc.) use {{PLACEHOLDER}} tokens in the HTML; main.cpp substitutes them at request time with String::replace().

First Flash (USB)

pio run --target upload

If upload fails ("Wrong boot mode"): Hold BOOT button, tap EN/RST, release BOOT — chip enters download mode. Then retry upload.

OTA Updates (after first flash)

  • From browser: open http://dmx-gateway.local/config → Firmware Update section → upload a .bin file or click "Update from GitHub"
Downloading update Back online
OTA progress After OTA
  • From IDE: uncomment in platformio.ini:
upload_protocol = espota
upload_port     = dmx-gateway.local
upload_flags    = --auth=dmxota

The OTA Password (Settings → Device) guards only this IDE / PlatformIO espota path. The browser "Firmware Update" above (.bin upload + GitHub install) is not protected by it.


First Setup

WT32-ETH01 (Ethernet)

Out of the box, connect an Ethernet cable and power on — DHCP assigns an IP automatically. Open http://dmx-gateway.local or check your router for the assigned IP. WT32-ETH01 also has WiFi: you can switch it to WiFi client or a standalone access point in /config → Network (see Network mode below).

1. Config Portal (WiFi client mode)

On first boot (or after WiFi reset), a board in WiFi client mode opens a WiFi access point:

  • SSID: DMX-Gateway (no password)
  • Connect with phone or PC → browser auto-opens portal (or go to 192.168.4.1)
  • Select your network, enter password, set Art-Net Universe (default: 0)
  • Click Save → LumiGate connects and reboots

Mesh / multi-AP WiFi: LumiGate scans all channels and joins the strongest AP for your SSID (and re-picks it on reconnect), so it won't latch onto a distant node. The ESP32 is 2.4 GHz only — make sure the AP nearest the device broadcasts 2.4 GHz. Check rssi on the status page: a healthy link is roughly −40 to −65 dBm. If it sits near −80 dBm right next to a router, the nearby node likely isn't offering 2.4 GHz — use a dedicated 2.4 GHz IoT SSID, or the WT32-ETH01 (Ethernet) build for a wired, rock-solid connection.

Network mode: WiFi, Ethernet, or standalone AP

Choose how LumiGate connects in /config → Network. Changes apply after a reboot.

  • Interface (boards with wired Ethernet — WT32-ETH01, v3, or any board with a W5500 module enabled under Wired Ethernet (W5500)): WiFi or wired Ethernet. WT32-ETH01/v3 default to Ethernet/DHCP; turn off "Use wired Ethernet" to run on WiFi. On a plain ESP32/ESP32-S3, first turn on "Use a W5500 Ethernet module" (off by default) and set its pins, then enable "Use wired Ethernet".
  • WiFi mode:
    • Client (STA) — join your existing 2.4 GHz network (the default; set credentials via the config portal above).
    • Standalone AP — LumiGate hosts its own WiFi network, so a phone, tablet, or console joins it directly with no router required. SSID = the device hostname (dmx-gateway by default); set a password of 8+ characters for WPA2, or leave it empty for an open network. The device is reachable at 192.168.4.1.
  • Ethernet → AP fallback (dual boards): if wired Ethernet is selected but the link is down at boot, LumiGate automatically brings up the WiFi AP so it stays reachable.

Using AP mode with an iPad / console app (Luminair, Photon, etc.): set WiFi mode to Standalone AP and reboot, join the dmx-gateway network, then point the app's Art-Net output at 192.168.4.1 (or broadcast 192.168.4.255).

AP-mode caveats: the joined device has no internet while on LumiGate's AP; it is 2.4 GHz only with a small client limit; and Art-Net (unicast/broadcast) is more reliable than sACN multicast over a SoftAP, so prefer Art-Net in this mode.

2. Status Page

Open http://dmx-gateway.local (or the IP shown in serial monitor at 115200 baud):

  • Live stats: framerate, RSSI, uptime, free heap, jitter
  • Conflict warning when sources clash on a universe, or a "merging" indicator when HTP/LTP merging is enabled for that output
  • Active sender list with per-sender protocol and FPS
  • All 512 DMX channels as a live grid — each cell shows the channel number large, its current value small, a value-proportional brightness, and a bottom-to-top gauge fill so levels read at a glance
  • Hover any cell for an instant (no-delay) 2.5× zoomed preview of that cell (channel number, value, gauge and label)
  • Click any cell → slider + sparkline history appear → set value directly
  • Change log with recent DMX activity

3. Changing WiFi / Config Reset

To move LumiGate to a different WiFi network, clear its stored credentials — it will reopen the setup portal on next boot.

Method Steps
Web (easiest) Open http://dmx-gateway.local/reset → click Reset WiFi → device reboots into AP mode
Hardware Power off → hold BOOT → power on → keep holding for 3 seconds → release → device reboots into AP mode

After reset, connect to the DMX-Gateway access point (no password) and follow the First Setup steps to join the new network.


Web UI

The HTTP server and WebSocket are served by ESPAsyncWebServer (non-blocking), so the web UI never stalls DMX output. Pages are gzip-compressed and dynamic values are fetched as JSON.

URL Method Function
/ GET Live status + 512-channel DMX grid (gzip)
/config GET / POST Change universe, protocol, per-output merge mode (off/HTP/LTP), static IP, hostname, OTA password, LED config, DMX pins (gzip)
/reset GET / POST Clear WiFi credentials, reboot to AP mode
/info.json GET Current settings + status (SSID, IP, universe, version, board/mcu id, etc.)
/dmx.json GET All 512 values, fps, rssi, uptime, heap, manual mode flag
/senders.json GET Active Art-Net / sACN senders (also pushed over the WebSocket)
/log.json GET Recent DMX change log entries (also pushed over the WebSocket)
/labels.json GET Channel labels object
/labels POST Store the full labels object (JSON body)
/version.json GET Current firmware version + update-available flag
/autoupdate POST Toggle auto-update (enabled=0/1)
/ota/upload POST Upload and flash a local firmware.bin
/ota/github POST Install a release from GitHub (version=latest or 1.0.N)

WebSocket (ws://<device>/ws, port 80)

Binary status/DMX frame pushed ~10×/s (528 + 2 bytes per output):

Bytes  0–1    fps × 10           uint16 big-endian (aggregate, all inputs)
Bytes  2–3    RSSI (dBm)         int16  big-endian
Bytes  4–7    free heap          uint32 big-endian
Bytes  8–11   uptime (s)         uint32 big-endian
Byte   12     active sender count uint8
Byte   13     source status       uint8 (0 = normal, 1 = conflict, 2 = merging)
Bytes  14–15  jitter × 10 (ms)   uint16 big-endian
Bytes  16–527 DMX ch 1–512       uint8[512] (the viewed output)
Bytes  528…   per-output fps × 10 uint16 big-endian × number of outputs

The web UI shows the viewed output's frame rate in the navbar and each output's own rate on its selector button. GET /dmx.json also carries "outfps":[…] alongside "fps".

Source merging (HTP / LTP)

When more than one console targets the same universe, pick a per-output merge mode in /config:

  • Off — last frame wins; a clash raises the conflict warning.
  • HTP (highest takes precedence) — each channel is the maximum across the active sources.
  • LTP (latest takes precedence) — the most recently seen source drives the output.

The sACN priority field is honoured first: a higher-priority source wins outright, and equal priority falls back to the mode above (Art-Net joins at the default priority of 100). A source that stops sending drops out of the mix after ~2.5 s. While an output is actively merging, the status page shows a green "merging" indicator instead of the conflict banner.

A JSON text frame with the sender list + change log is pushed every 2 s ({"meta":1,"senders":[…],"log":[…]}) so the browser never has to poll.

Browser → ESP32 (JSON text):

{ "type": "set",      "ch": 1,  "val": 200 }
{ "type": "mode",     "manual": true       }
{ "type": "blackout"                       }
{ "type": "identify", "ch": 5              }

Channel labels are managed over REST (GET /labels.json, POST /labels).


Visual pin configuration (board picker)

LumiGate has a lot of GPIOs to set (status LED, 5-LED panel, OLED, two DMX outputs). To make this idiot-proof, Settings → Hardware board offers:

  • Templates — pick your board and click Apply template to fill every LED / display / DMX pin with the tested map in one step. Selecting the LumiGate v3 board applies the exact pin map of the open-hardware PCB.
  • Click pins on a board diagram — the pick button next to each GPIO field opens an interactive board so you click the actual pin instead of guessing GPIO numbers. The diagram colour-codes pins (free / caution / do-not-use) and shows your current assignments right on the board.
  • Live validation — duplicate pins, strapping/flash/input-only pins and Ethernet-reserved pins are flagged in red/amber before you can save.

Five boards are built into the firmware and work fully offline, covering the common variants (which are not all the same pinout):

Board Notes
LumiGate v3 our board (ESP32-S3 + W5500); preset generated from the PCB source
ESP32 DevKitC (WROOM-32, 38-pin) breaks out the flash pins too
ESP32 DevKit v1 (DOIT, 30-pin) narrower, no flash pins on the header
ESP32-S3 DevKitC-1 (44-pin) GPIO33-37 only free on no-PSRAM modules
Seeed XIAO ESP32-S3 tiny board, D0-D10 silk

The full catalog covers 33 boards spanning every esp32 / esp32s3 board the firmware runs on (DevKit / DOIT / NodeMCU, WEMOS LOLIN, Adafruit Feather / QtPy / HUZZAH32 / Metro, Heltec OLED, Olimex PoE / Gateway / wESP32 Ethernet, Unexpected Maker, M5Stack, LilyGO, SparkFun ...). The boards beyond the five built-ins are pulled lazily from the online catalog (web/boards/, served via GitHub Pages) and cached in your browser; no network means you still get the built-ins plus manual entry. You can switch boards from a dropdown inside the pin-picker popup itself.

All descriptors are generated from authoritative pinout data (hardware/gen_board_descriptor.py): hardware/lumigate.py for v3, published header pinouts for the hand-tuned dev boards, and the Arduino core variants/<dir>/pins_arduino.h for the rest, so the GPIO numbers and reserved/strapping/flash flags are accurate per variant (e.g. the PICO-based Feather V2 frees GPIO6-11 and reserves 16/17 instead). The picker draws its own horizontal diagram for every board; there are no board photos or realistic graphics.

See docs/pin-picker.md for the design, the descriptor schema and how to add a board.


QLC+ Setup

  1. Open Eingänge/Ausgänge tab
  2. Click in the output area right of Universe 1 → select ArtNet → interface = your PC's IP
  3. Configure: Output IP = LumiGate's IP, Art-Net Universe = 0
  4. Set transmission mode to Full (sends all 512 channels continuously at ~40 fps)
  5. Test with Einfaches Mischpult → move fader → grid lights up cyan

Universe mapping: QLC+ "Universe 1" = Art-Net Universe 0. LumiGate defaults to Universe 0.
Tip: if "Standard" mode shows ~0.4 fps and values don't update, restart QLC+ or switch to "Full" mode.

Ready-made demo shows

Two QLC+ workspaces are included to test fixtures and show off the gateway — open with File → Open, map Universe 1 → ArtNet (above), switch to Operate mode, and press the Virtual Console buttons:

File Description
docs/qlcplus-demo.qxw Small 6-channel demo: fade, blink, running light
docs/qlcplus-show.qxw 30-minute, 20-channel show — sine waves, comets, sparkle, VU bounce, crossfades, strobes, build-ups and more, sequenced into an intro→build→peak→groove→outro arc. Press GREAT SHOW.

The big show is generated by docs/make_show.py — edit the arc list and re-run to customize.


Status LED

LumiGate supports three LED types, configurable in the web UI:

State WS2812 RGB Plain GPIO
Booting white on
Connecting to WiFi blue blink blink
Config portal / AP active purple on
No WiFi (lost) red blink (fast) off
Idle (connected, no DMX) amber heartbeat (½ s on/off) pulse
DMX active solid green solid

The "DMX active" green is held through brief input gaps (continuous 40 Hz output), so momentary multicast loss doesn't flicker the LED. The LED runs on its own task, so serving the web UI never freezes it.

5-LED status panel (ledType 3, the LumiGate v3 board) — five discrete LEDs that show independent states simultaneously, instead of one LED time-sharing colours:

LED Meaning
Red no network (blink)
Green network up (solid)
Yellow DMX activity (fast blink while frames flow)
Blue source conflict — two senders on one universe (slow blink)
White identify active (blink) / booting

Default GPIO: 2 (ESP32 DevKit on-board LED). ESP32-S3 DevKitC-1 uses GPIO 48 (built-in WS2812). The v3 board uses R=1 G=2 Y=6 B=7 W=15.


Display (OLED / colour OLED)

LumiGate can drive an optional status display that shows the gateway's live state without a browser — IP, output universe(s), protocol, frame rate, source count and link / DMX activity — and auto-switches to a full-screen alert when something needs attention (two consoles fighting over the universe, identify, or manual override). It's off by default; enable and pin it under Settings → Display.

LumiGate display types

Rendered from the firmware's own layout code with the real display font  ·  regenerate with docs/make_display_preview.py. Top: 0.96″ / 1.3″ mono OLED (status, then the conflict / identify / manual banners). Middle: the same panel in blue and yellow/blue-split, and the 0.91″ 128×32 compact layout. Bottom: the 1.5″ SSD1351 colour panel, which mirrors the RGB status-LED language (green = live, amber = idle, red = conflict).

The status screen adapts to the number of outputs: a single output shows Uni 0 with one frame rate, while two enabled outputs get a row each (A/B) with their own universe and own FPS (shown above). The source count is labelled Sources (active Art-Net / sACN senders).

Supported panels

Panel Controller Bus Pins Boards
0.96″ / 1.3″ OLED 128×64 SSD1306 / SH1106 I²C 2 all
0.91″ OLED 128×32 SSD1306 I²C 2 all
1.5″ colour OLED 128×128 SSD1351 SPI ~5 ESP32 / ESP32-S3 (not the Ethernet boards)

Mono panels are 1-bit — white, blue and yellow/blue-split versions all behave identically (the colour is the physical emitter, not addressable). The I²C address is auto-detected (0x3C / 0x3D). SPI colour panels need ~5 pins, so they only fit the non-Ethernet boards.

Settings (Settings → Display)

Field Notes
Display type off / SSD1306 128×64 / SSD1306 128×32 / SH1106 128×64 / SSD1351 colour
SDA, SCL I²C pins (mono panels). Avoid strapping pins like GPIO12; WT32-ETH01 → 14 / 15
CS, DC, RST, SCK, MOSI hardware-SPI pins (colour panel)
Orientation normal / flipped 180°

See docs/display.md for the full design — per-board pin tables and layout details.


DMX Outputs

LumiGate drives up to 2 independent DMX outputs — each its own universe, UART port and RS485 transceiver — configurable at runtime under Settings → DMX Outputs (no recompile). The 2-output ceiling is a hardware limit: the ESP32 / ESP32-S3 expose 3 UARTs and UART0 is the serial console, leaving UART1 + UART2. Output B ships disabled, so single-universe setups are unchanged, and devices updated from older firmware keep their existing output as Output A.

Per output:

Setting Default (Output A / B) Description
Enabled A on / B off Whether this output drives a DMX line
Universe 0 / 1 Art-Net universe (sACN universe = this + 1). Range 0–15
UART port 1 / 2 Which ESP32 UART drives the line. Each enabled output needs a distinct port
TX pin 17 (GPIO17) ESP32 GPIO that drives the RS485 module's DI (data-in)
RX pin 16 (GPIO16) ESP32 GPIO connected to the RS485 module's RO (data-out); needed for RDM
RTS / DE pin −1 (disabled) RS485 direction-control pin. −1 for auto-direction modules (Waveshare C). Required for RDM.

Setting both outputs to the same universe turns LumiGate into a 1-in / 2-out DMX splitter. RDM runs on the first enabled output with an RTS pin set (one output at a time). For multi-universe input, Art-Net is recommended (single UDP socket, no IGMP); sACN works too (one multicast group joined per universe).

WT32-ETH01: the Ethernet PHY consumes most GPIOs, so a second output is best run output-only (TX, no RX/RDM).

Per-board defaults & capabilities

Applied on first boot; everything is overrideable in the web UI (no recompile).

Board Build env Net Outputs Output A — TX / RX / RTS Suggested Output B Notes
ESP32 DevKit (WROOM-32) esp32dev WiFi 2 GPIO17 / 16 / −1 UART2, TX 32, RX 33 Plenty of free GPIO; RDM possible on both
ESP32-S3 DevKitC-1 esp32s3dev WiFi 2 GPIO17 / 16 / −1 UART2, TX 18 (RX −1) LED = WS2812 on GPIO48; v3 build disables the brownout detector (CONFIG_ESP_BROWNOUT_DET=n, a from-source build) to avoid an S3 boot-loop
WT32-ETH01 wt32eth01 Ethernet 2 GPIO4 / 5 / −1 UART2, TX-only GPIO16 = LAN8720 PHY power, so pins are shifted; 2nd output best TX-only (no RX/RDM)
LumiGate v3 (ESP32-S3 + W5500) lumigate_v3 Ethernet (W5500 SPI) 2 GPIO17 / 18 / 8 UART2 Open-hardware board (hardware/). 5-LED status panel; W5500 on SPI3 (CS=10/INT=14/RST=9); RTS/EN=8 for RDM direction

Any ESP32 / ESP32-S3 build can add wired Ethernet with an external W5500 module — the W5500 driver is compiled into every build. In /config → Wired Ethernet (W5500) turn on Use a W5500 Ethernet module (off by default), set its pins, then enable Use wired Ethernet. Pins default to the classic-ESP32 VSPI set (CS=5 / SCK=18 / MOSI=23 / MISO=19 / INT=4 / RST=25) and are fully configurable (with the on-board pin-picker); lower the SPI clock there if a long-wired module isn't detected. No special build is needed.

All boards drive two outputs (UART1 + UART2). A 2nd output (UART2) used to panic on the ESP32-S3 — a latent esp_dmx 4.1.0 bug where the UART2 entry was guarded by an enum the preprocessor reads as 0, so it was compiled out (null peripheral pointer). That, plus an ESP-IDF 5.x fix (the removed uart_periph_signal[].module field) needed once the firmware moved to arduino-esp32 v3, is applied by a build-time patch in extra_scripts.py.

ESP32-S3 — safe GPIOs for a 2nd output: free choices are 5, 6, 7, 8, 15, 18, 21. Avoid 26–37 (SPI flash / octal PSRAM — will crash), 19/20 (USB), 43/44 (serial console), 0/45/46 (strapping), 48 (WS2812 LED), and Output A's 16/17.

Using a different RS485 module? Wire your module's DI (data-in) to the TX GPIO and RO (data-out) to the RX GPIO, then set the pins under Settings → DMX Outputs.

Upgrade & crash safety

  • Existing single-universe devices are unaffected by the update. Migration maps the old single-output config to Output A and leaves Output B disabled, so exactly one driver is installed on UART1 — byte-for-byte the same path as before.
  • No configuration can brick the device. A safe-boot guard persists a crash counter around DMX init; if init ever panics (a bad pin/port), the next boots progressively disable outputs (keep A → all off) until the web UI is reachable. An enabled output with no TX pin is sanitized off automatically (firmware + a client-side check).

Persistent Configuration (NVS)

Key Default Change via
Art-Net Universe 0 Web /config or portal
Protocol Both (Art-Net + sACN) Web /config
Static IP / gateway / subnet / DNS DHCP Web /config (Network)
Auto-update off Web /config (Firmware)
Channel labels Status page (channel modal)
Hostname dmx-gateway Web /config
OTA Password (IDE espota only) dmxota Web /config
LED type / GPIO pin board default Web /config (Status LED)
Per-output: enabled / universe / UART port / TX / RX / RTS A on (uni 0, UART1, TX=17, RX=16, RTS=−1); B off Web /config (DMX Outputs)
Display type / pins off Web /config (Display)
WiFi credentials Config portal or /reset

Testing

End-to-end Playwright tests drive a live device — they inject real Art-Net and sACN/E1.31 packets over the network and assert the REST API, WebSocket and web UI react correctly (Art-Net/sACN → DMX, sender tracking, conflict banner, change log, manual override + blackout, labels, and the 2-output feature). See docs/tests/README.md.

cd docs && npm install && npx playwright install chromium
LUMIGATE_HOST=dmx-gateway.local npm test

License

MIT — do whatever you want, attribution appreciated.

About

Open-source Art-Net / sACN (E1.31) → DMX512 gateway for ESP32 / ESP32-S3 / Ethernet. Live web UI shows all 512 channels in real time, flags console conflicts, and drives galvanically-isolated DMX. Flash from your browser, no toolchain.

Topics

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors