The core layer of the OpenLamp family: instant, 100% local control of smart LED lamps (Tuya today, WLED experimental), exposed through a stable command contract — OpenLamp State (OLS), a WLED-compatible JSON state patch (see OLS.md).
Part of the OpenLamp family:
| Layer | Repo | Role |
|---|---|---|
| Engine (this repo) | openlamp/engine |
drivers + dispatcher + local API + daemon + CLI (Python reference) |
| Engine, JS port | engine-js | same contract on Node/tuyapi — for JS-first environments |
| Stream Deck frontend | lumideck | keys, dials, live status on an Elgato Stream Deck |
| MIDI frontend | openlamp-midi | stage control from physical MIDI controllers |
engine.py— the engine: one thread per lamp with a persistent connection (sub-200 ms commands), the OLS dispatcher, groups, snapshots, animations (cycle/flash/tempo), connect-time sync, a rainbow welcome sweep, and the local API on127.0.0.1:8377(/cmd,/status,/syntax, plus the optional WLED-compat/json/state). Frontend-agnostic: its only upward link is anon_changehook.daemon.py— headless host: runs the engine without any frontend app.run-headless.sh— one command to switch to CLI/MIDI-only mode.lamp-doctor.sh(macOS) — one-command diagnosis of "lamps unreachable", testing the three causes in order: Mac on the wrong Wi-Fi / router down / lamp powered off or radio-napping. Never trust the router's web panel (it renders from service-worker cache even with the router dead) — this script is the ground truth.lamp.py— the CLI (also Bome-callable):lamp.py vert,lamp.py bri:40… Routes through the local API when a host runs, drives lamps directly otherwise.com.benlab.lumideck-daemon.plist— launchd autostart for the daemon.OLS.md— the OpenLamp State contract.TUYA-KEYS.md— how to get your lamps' local keys (official Tuya cloud API, one-time).
A Tuya lamp accepts a single local connection, and every host binds port 8377.
So run either the Stream Deck plugin (it embeds this engine in-process) or
daemon.py — never both. Deck sessions → plugin; CLI/MIDI-only sessions → daemon.
OLS uses 8-bit for brightness and per-channel color, for three reasons:
- WLED compatibility — WLED's JSON API is 8-bit; OLS is a compatible patch.
- It matches the hardware — RGB LEDs are driven 8 bits per channel (16.7 M colors); Tuya's internal 0–1000 scale adds no perceptible precision.
- It matches perception — ~1 % brightness steps are at the threshold of what the eye distinguishes; 256 levels cover that. MIDI frontends (7-bit, 0–127) scale up ×2 — plenty for stage cues.
tuya-lamps.json sits next to lamp.py (never committed — it contains your local
keys). Template:
{
"lamps": [
{"name": "L1", "mac": "aa:bb:cc:dd:ee:ff", "device_id": "…", "local_key": "…",
"ips": {"192.168.1": "192.168.1.50"}}
],
"groups": {"front": ["L1"]},
"sync": {"enabled": true, "state": {"on": true, "col": [0, 100, 200], "bri": 153}}
}Made by BenLab with the help of Claude. WLED support is written but untested on real hardware — testers wanted: open an issue on lumideck.