Local-First Tuya Smart Bulb Control | Live Demo
CLI, Python library, and HTTP API for controlling Tuya-based RGBCW smart bulbs directly over the LAN β no cloud round-trip required. Includes named colour presets, smooth transitions, scene orchestration, device discovery, network diagnostics, and an automation engine.
Built with Python 3.11+ and TinyTuya.
Most smart-light ecosystems push everything through a cloud service, adding latency and a single point of failure. This project takes the opposite approach: talk to the bulbs directly over the local network using the Tuya protocol, falling back to cloud only for initial device discovery and stale-IP refresh.
What started as a quick script to toggle a desk lamp grew into a full service layer with a CLI, HTTP API, scene definitions, device registry, automatic fallback/retry, transition effects, and network diagnostics β designed to be extended with menu-bar controls, webhook automations, or anything else that can call a Python function.
- Local LAN control β on/off, brightness, white mode, HSV colour, raw DPS commands
- 25 named colour presets β
sunset-orange,ocean-blue,candlelight,lavender, and more - Smooth transitions β
fadeto a brightness over N seconds,breathewith configurable cycles - Scene engine β declarative multi-device scenes defined in JSON (
movie-time,party, etc.) - Device registry β slug/name/room/group resolution; target
all, a room, a group, or a single bulb - Auto-discovery β runs the TinyTuya wizard non-interactively to import new bulbs
- Refresh & fallback β if a command fails due to a stale IP, the service rescans the LAN and retries
- Network diagnostics β TCP probe, subnet comparison, VPN detection, SSID check, and human-friendly likely-cause hints
- Web dashboard β glassmorphism dark-themed control panel with device cards, colour presets, scenes, transitions, and keyboard shortcuts (live demo)
- HTTP API β full-featured FastAPI server for integration with Home Assistant, iOS Shortcuts, etc.
- Automation primitives β trigger-to-scene rules ready for future menu-bar or webhook integrations
- Clean CLI β every operation available as a
smart-lightssubcommand with JSON output
git clone https://github.com/dr-nico-f/smart-light-controller.git
cd smart-light-controller
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]" # core + test dependencies
pip install -e ".[api]" # adds HTTP API server (FastAPI + uvicorn)Copy the example configs and fill in your credentials:
cp -r config.example/ config/Edit config/cloud.json with your Tuya IoT Platform credentials, then run discovery:
smart-lights discover --max-time 8This populates config/devices.json automatically. Scene definitions go in config/scenes.json (see config.example/ for the format).
All config files are gitignored β they contain device keys and local IPs.
smart-lights devices # list all configured bulbs
smart-lights on living-room # turn on a room
smart-lights off all # everything off
smart-lights dim living-room 40 # set brightness (0-100)
smart-lights white living-room --brightness 80 --colourtemp 50smart-lights colours # list all 25 presets
smart-lights color living-room sunset-orange # use a named preset
smart-lights color living-room --h 240 --s 800 --v 600 # raw HSVsmart-lights fade living-room 80 --from 0 --duration 5 # 5-second fade
smart-lights breathe living-room --cycles 4 --duration 3 # breathing pulsesmart-lights scenes # list available scenes
smart-lights scene movie-time # apply a scenesmart-lights discover --max-time 8
smart-lights refresh
smart-lights diagnose all --timeout 2
smart-lights status allsmart-lights serve --port 8000Open http://localhost:8000 for the web control dashboard β a glassmorphism dark-themed control panel featuring:
- Device cards grouped by room with on/off toggles and brightness sliders
- 25 colour presets + custom colour picker with per-device targeting
- Scene buttons with thematic colour feedback on device cards
- Fade and breathe transition controls
- Keyboard shortcuts (
1β9toggle,Escall-off,Ffade,Bbreathe)
Try the live demo (runs in demo mode with mock data).
Interactive API docs are at http://localhost:8000/docs. You can also use the API directly:
curl http://localhost:8000/devices
curl -X POST http://localhost:8000/devices/living-room/on
curl -X POST http://localhost:8000/devices/living-room/colour \
-H "Content-Type: application/json" \
-d '{"preset": "ocean-blue"}'
curl -X POST http://localhost:8000/scenes/movie-timesrc/smart_lights/
cli.py β argparse CLI (devices, status, on, off, dim, color, fade, breathe, serve, β¦)
service.py β application service layer with auto-retry and fallback
tuya_client.py β TinyTuya local and cloud API wrappers
bulbs.py β high-level single-bulb operations
models.py β typed dataclasses (DeviceConfig, CloudConfig, SceneConfig, CommandResult, β¦)
config.py β JSON config loading and path resolution
registry.py β device inventory with slug/name/room/group resolution
scenes.py β scene loading and lookup
colours.py β 25 named colour presets with HSV values
transitions.py β fade and breathe transition effects
automation.py β trigger-to-scene automation engine
api.py β FastAPI HTTP server
static/index.html β web control dashboard (dark theme, no build step)
py.typed β PEP 561 type-checking marker
config.example/ β sanitized example configs (copy to config/ and edit)
tests/ β 53 pytest tests with fixture-based isolation
.github/workflows/ β CI: pytest + mypy across Python 3.11β3.13
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β CLI (cli.py) HTTP API (api.py) β
β argparse β JSON stdout FastAPI β JSON responses β
ββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββ
β Service Layer (service.py) β
β coordinates registry, scenes, local/cloud clients β
β auto-retry with metadata refresh on connectivity error β
βββββ¬βββββββββββββββ¬βββββββββββββββ¬ββββββββββββββββββββββββ
β β β
βββββΌββββ βββββββΌββββββ βββββΌβββββββββββββββββββββ
β Device β β Scene β β Automation Engine β
βRegistryβ β Library β β trigger β scene runner β
βββββ¬βββββ βββββββββββββ βββββββββββββββββββββββββββ
β
βββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Bulb Controller (bulbs.py) β
β + Colour Presets (colours.py) β
β + Transitions (transitions.py) β
ββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββ
β Tuya Clients (tuya_client.py) β
β LocalTuyaClient: LAN control, scan, probe, wizard β
β CloudTuyaClient: metadata fetch, status, functions β
ββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββ
β
βββββββββΌββββββββ
β TinyTuya β
β (LAN + Cloud)β
βββββββββββββββββ
Local-first by design. Every command flows through LocalTuyaClient, which opens a direct TCP connection to the bulb on port 6668. Cloud credentials are only used for initial device discovery (the TinyTuya wizard needs them to fetch device keys) and fallback IP refresh when a bulb becomes unreachable. The service layer automatically detects connectivity errors (TinyTuya codes 901β904), rescans the network, and retries β so stale DHCP leases are handled transparently.
| Language | Python 3.11+ |
|---|---|
| Protocol | Tuya local protocol v3.3 via TinyTuya |
| HTTP API | FastAPI + Uvicorn |
| Packaging | pyproject.toml + setuptools |
| Config | JSON files in config/ (gitignored) |
| Testing | pytest (53 tests, fixture-isolated) |
| CI | GitHub Actions (Python 3.11, 3.12, 3.13) |
| Type checking | mypy with py.typed marker |
pip install -e ".[dev,api]"
pytestThe test suite covers config loading, device registry resolution and merge logic, scene retrieval, service layer operations with fallback behaviour, colour preset resolution, transition interpolation, and HTTP API endpoints β 53 tests across 7 modules, all fixture-based and runnable after a clean clone.
| Method | Path | Description |
|---|---|---|
GET |
/devices |
List all configured devices |
GET |
/scenes |
List available scene names |
GET |
/colours |
List colour presets |
GET |
/devices/{target}/status |
Get device/group status |
POST |
/devices/{target}/on |
Turn on |
POST |
/devices/{target}/off |
Turn off |
POST |
/devices/{target}/brightness |
Set brightness |
POST |
/devices/{target}/white |
Set white mode |
POST |
/devices/{target}/colour |
Set colour (preset or HSV) |
POST |
/devices/{target}/fade |
Smooth brightness fade |
POST |
/devices/{target}/breathe |
Breathing pulse effect |
POST |
/scenes/{name} |
Apply a scene |
GET |
/diagnose/{target} |
Run network diagnostics |
- Requires Python 3.11+ (developed on 3.12)
- Packaging via
pyproject.toml; install withpip install -e ".[dev,api]" - Uses
tinytuyaas the sole runtime dependency;fastapi+uvicornfor the optional HTTP API - Config files are gitignored; see
config.example/for the expected format - All CLI output is JSON for easy scripting and composition
- Structured logging via Python's
loggingmodule at key service layer points
MIT Β© 2025β2026 β Created by Nico
