WLED Controller — An Evening Hack That Works
I have WLED light strips scattered around my setup. Controlling them meant opening a browser, typing IPs, adjusting sliders separately. There had to be a better way.
So I built one. In an evening.
The Problem
WLED is brilliant — open-source, standalone, no cloud. Each light gets its own IP. But there's no unified controller app. You're stuck:
- Opening browser for each light individually
- No way to save "scenes" (gaming mode, sleep mode, etc.)
- Copy-pasting RGB values between devices is manual
- No automation on startup or shutdown
What I Built
WLED Controller: A Python GUI that lets you:
- Auto-discover WLED devices on the network (network scanning with threading)
- Control all lights at once — brightness, RGB color, effects, speed, intensity
- Save scenes — capture current state, replay it later
- One-click startup — run on Windows boot, execute default scenes automatically
- Copy/paste colors — hex color input for quick reuse across devices
- Read actual device state — sliders show real brightness/color on app load
How It Works
Architecture
┌─ Scenes Tab ────────────────────────┐
│ Save/load lighting configurations │
├─ Lights Tab ────────────────────────┤
│ Control all devices, adjust colors │
├─ Settings Tab ──────────────────────┤
│ Add devices, scan network, startup │
└─────────────────────────────────────┘
Stack
- CustomTkinter — clean dark UI, no bloat
- WLED JSON API —
/json/stateendpoint with segment-based control - Network scanning — threaded IP range checks, finds devices in seconds
- Local JSON config — no cloud, no login, no tracking
- urllib only — no external HTTP library dependency
The API Trick
WLED's API isn't /api/state, it's /json/state. And colors/effects need to be in a "segment" object:
POST /json/state
{
"seg": [{
"sx": 127,
"ix": 200,
"col": [[255, 0, 128]]
}]
}
Where:
- sx = effect speed (0-255)
- ix = effect intensity (0-255)
- col = RGB color array
- seg = segment (WLED groups LED settings into segments; we target segment 0)
Once I found that, the rest fell into place.
Key Features in Detail
Scene Capture
Click [Edit] on a scene, then Capture Current State. It reads all devices and saves their current settings (brightness, color, effect, speed, intensity).
Click the ★ button to mark a scene as the default startup scene — runs automatically on boot.
Hex Color Input
Each device shows a hex color field that updates as you adjust RGB sliders. Copy the color, paste it into another device's hex field, click Apply. Instant consistency across all lights.
State Sync on Startup
When the app launches, it fetches the actual state from each WLED device:
[Sync] Device Name: on=True, bri=68, fx=0, sx=128, ix=128, rgb=(255,166,52)
Sliders reflect reality. If you manually adjusted a light via browser, the app sees it.
Windows Startup
Settings tab has a checkbox: "Run on Windows startup". Toggle it on, restart Windows, the app launches automatically and runs your default scene.
What Made This Work
- No external dependencies (besides CustomTkinter) — just urllib for HTTP
- Parallel network scanning — finds 10 devices in ~3 seconds with threading
- Segment awareness — understand WLED's segment model, don't fight it
- Graceful state sync — read actual device state, don't assume UI reflects reality
- Color preservation — changing RGB doesn't override brightness (took a minute to debug)
The Evening Timeline
- 9:00 PM — realized the problem
- 9:30 PM — basic device discovery working
- 10:15 PM — brightness/color sliders functional
- 11:00 PM — scenes saving and replaying
- 11:45 PM — scene capture, defaults, shutdown integration done
- Next day — hex input, state sync, startup toggle, blog post
Feature-complete and shipped.
Next Steps
This controller now lives in the Indigo-Nx ecosystem alongside:
- ViewShift — display manager with scene switching
- Vessel — local AI voice assistant
- DevScan — USB/HID device scanner
All using the same unified theme, same warning screen, same local-first philosophy.
Lessons
- Minimal scope wins. I didn't build a preset browser, custom palettes, or grouping. Just what I needed.
- Read the docs. The WLED docs had everything; I just had to look for
/jsoninstead of/api. - Threading is free performance. Network calls don't block the UI.
- Local > Cloud. No auth, no rate limits, no data harvesting. Just JSON on disk.
- Get it working, ship it. Don't over-engineer an evening hack.
Status
Feature-complete and working. Part of the Indigo-Nx ecosystem.