Skip to main content

One-shot CLI to control Tuya smart lamps locally (colour, brightness, on/off) — the OpenLamp/LumiDeck command-line frontend.

Project description

OpenLamp Engine

The core layer of the OpenLamp family: instant, 100% local control of smart LED lamps (WLED recommended, Tuya also supported), exposed through a stable command contract — OpenLamp State (OLS), a WLED-compatible JSON state patch (see OLS.md).

PyPI — openlamp-lamp

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

Install the lamp CLI (PyPI)

The one-shot command-line frontend is published as openlamp-lamp on PyPI:

pip install openlamp-lamp

lamp rouge            # set colour (keeps brightness)
lamp bri:60           # brightness 1-100
lamp veilleuse        # a brightness preset (lueur/veilleuse/tamise/moyen/fort/max)
lamp on | off

It finds your lamp config (with local keys) from $LUMIDECK_LAMPS, else ~/.config/openlamp/tuya-lamps.json (template at the bottom of this README). If the LumiDeck plugin/daemon is running, the CLI routes through its local API (127.0.0.1:8377) for instant response; otherwise it drives the lamps directly.

Only the CLI (lamp.py) ships on PyPI. The engine + headless daemon stay in this repo (they couple to a local file layout — a clean engine package is a follow-up).

What's inside

  • 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 on 127.0.0.1:8377 (/cmd, /status, /syntax, plus the optional WLED-compat /json/state). Frontend-agnostic: its only upward link is an on_change hook.
  • 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.
  • bin/lamp-bench.py — a small bench for WLED tinkerers: firmware/hardware info, command latency (the round-trip a press pays), and — opt-in — the command ceiling (--ceiling) the lamp sustains before dropping commands. stdlib only. lamp-bench.py <ip>.
  • com.openlamp.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).

One host at a time — the rule

Every host binds port 8377 (and a Tuya lamp additionally accepts only one local connection). 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.

Why 8-bit values (0–255)

OLS uses 8-bit for brightness and per-channel color, for three reasons:

  1. WLED compatibility — WLED's JSON API is 8-bit; OLS is a compatible patch.
  2. 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.
  3. 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.

Config

You can point the engine at an external, synced folder (Google Drive/Dropbox) that holds lamp.py + tuya-lamps.json by setting OPENLAMP_LAMPS_DIR to that path; otherwise 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}}
}

Test hardware (budget setup)

The engine and LumiDeck are developed and tested on this cheap, off-the-shelf rig — reproduce it for well under €40 for a two-lamp stereo stage:

  • Bulbs — Athom WLED 7 W Color Bulb (E27, ESP32-C3, RGB + tunable white, WLED-preflashed, ~€13 each). WLED ships already flashed — put the bulb on Wi-Fi and it's auto-discovered. This is the reference bulb every timing figure is measured on (~45 ms/command, the latency-comp floor beatsync uses).
  • Socket / holder — TobeBright E27 corded lamp holder with inline switch (up to 100 W, a few € on Amazon). Turns a bare bulb into a standalone plug-in stage lamp — no fixture required, just screw the bulb in and plug it to mains.

Publishing to PyPI (maintainer)

openlamp-lamp publishes via Trusted Publishing (OIDC) — no token in the repo. .github/workflows/publish.yml builds + publishes on each GitHub Release. One-time:

  1. PyPI → Add a pending publisher (https://pypi.orgPublishing): project openlamp-lamp, owner openlamp, repo engine, workflow publish.yml, environment pypi.
  2. GitHub → Settings → Environments → Newpypi.
  3. Bump version in pyproject.toml, commit, cut a GitHub Release (tag v0.1.0) → the workflow builds and publishes. Then pip install openlamp-lamp works everywhere.

Credits

Made by @Beennnn (OpenLamp) with the help of Claude. WLED is the recommended, tested path (validated on Athom RGBCW bulbs, ~45 ms/command). Feedback: open an issue on lumideck.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

openlamp_lamp-0.1.2.tar.gz (22.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

openlamp_lamp-0.1.2-py3-none-any.whl (18.6 kB view details)

Uploaded Python 3

File details

Details for the file openlamp_lamp-0.1.2.tar.gz.

File metadata

  • Download URL: openlamp_lamp-0.1.2.tar.gz
  • Upload date:
  • Size: 22.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for openlamp_lamp-0.1.2.tar.gz
Algorithm Hash digest
SHA256 3c78b8b9abbfbfd12fc378b3a5d3210473127b7cb41c4392e3fc5281968f4b91
MD5 7ea5b163effff0ff9a7602d76e42c54c
BLAKE2b-256 cedfbbea02d5bef8edcc53475320d2ceb5117b2c5826956cf1fa69bf4d54586c

See more details on using hashes here.

Provenance

The following attestation bundles were made for openlamp_lamp-0.1.2.tar.gz:

Publisher: publish.yml on openlamp/engine

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file openlamp_lamp-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: openlamp_lamp-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 18.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for openlamp_lamp-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 aa04a15c4ee6676a2f17f56f49b4438fed8fa339a942f50d51d3cb45ab99fe2b
MD5 e4a44a2803805d2ef9632db2c2d079f1
BLAKE2b-256 8c81a1ab27118c2dbac30328a6de0169567162b1942e4e683500dd101dda1232

See more details on using hashes here.

Provenance

The following attestation bundles were made for openlamp_lamp-0.1.2-py3-none-any.whl:

Publisher: publish.yml on openlamp/engine

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page