# tuya-re Control your Tuya devices locally — no cloud, no phone app, and (for the direct paths) no gateway. tuya-re is a from-scratch implementation of Tuya's local protocols: after a one-time credential pull, your commands never leave your machine or your LAN. The project merges three earlier proofs of concept into one tool. It drives four device paths: | Device family | Transport | Command group | |---|---|---| | Fingerbots | Bluetooth LE (Tuya BLE v3) | `ble` | | Wi-Fi switches and plugs | LAN (Tuya local v3.3 and v3.4) | `switch` | | IR blasters (GWW-RC03 / JMIR05) | Bluetooth LE (Tuya BLE v3) | `ir` | | Fingerbots behind a Tuya BLE gateway | LAN, via [tinytuya](https://github.com/jasonacox/tinytuya) | `gateway` | The BLE and LAN stacks (framing, crypto, sessions) are hand-written on top of `bleak` and `pycryptodome`. Only the gateway path delegates protocol work to `tinytuya`. ## Prerequisites - [uv](https://docs.astral.sh/uv/) — all commands run through it. - Python 3.11 or later (development and CI use 3.14; uv installs it for you). - For BLE commands on macOS: run from Terminal or iTerm and approve the Bluetooth permission prompt on first use. A process launched without a GUI parent is stopped by the OS instead of prompted. ## Install ```bash git clone https://github.com/swackhamer/tuya-re.git cd tuya-re uv sync ``` ## Configure the project 1. Copy the template: ```bash cp config.example.toml config.toml ``` 2. Get your device credentials. At , create a cloud project, link your Smart Life app account (**Devices** > **Link App Account**, then scan the QR code from the app), and open **Cloud** > **API Explorer** > **Query Device Details** (`GET /v1.0/devices/{id}`). The response contains `local_key`, `uuid`, and `product_id`. 3. Fill in `config.toml`. Configure only the sections you need — each maps to one command group. `config.toml` is git-ignored and is the only place real credentials belong. For what's secret and how to rotate keys, see [SECURITY.md](SECURITY.md). Notes: - The cloud response's `ip` is your public WAN address, not the device's LAN address. Leave `ip` unset and let UDP discovery find the real one. - The `local_key` rotates when a device is re-paired in the app. If control starts failing, re-pull the key and update `config.toml`. - For the gateway path, the gateway's `local_key` and the Fingerbot's `node_id` come from `uv run python -m tinytuya wizard` (delete its output files after copying the values). ## Usage Global flags (`-v`, `--device`, `--timeout`, `--config`) go before the command. `--device` selects a named device, `all` for every configured device of that type, or omit it for the configured default. Every command group is available under the unified entry point, `uv run tuya-re `. The standalone `fingerbot`, `switch`, and `ir` scripts are shortcuts that skip the group prefix. ### Fingerbot over Bluetooth ```bash uv run fingerbot devices # list configured names and the default uv run fingerbot scan # find the device, print its address uv run fingerbot status # connect, pair, dump datapoints uv run fingerbot click # press the default named device uv run fingerbot --device desktop click uv run fingerbot press --duration 3 # press and hold for 3 seconds uv run fingerbot --device all click --repeat 10 # all robots, in parallel ``` The tool scans for the Tuya BLE service, matches the uuid decrypted from the advertisement, runs the handshake (`DEVICE_INFO`, derive the AES session key from `srand`, then `PAIR`), and writes the click datapoint with a guaranteed rising edge. `--repeat` and `--interval` fire multiple clicks over one connection; the arm needs about 1.5 seconds per press-and-release cycle. ### Switch over the LAN ```bash uv run switch status # discover, connect, report on or off uv run switch on uv run switch off uv run switch toggle uv run switch --device all off # every switch, concurrently ``` `switch` finds the device by UDP broadcast (ports 6666/6667), then opens an encrypted TCP session on port 6668 keyed by `local_key`. Protocol v3.3 uses AES-ECB framed messages; v3.4 negotiates a session key first, then uses HMAC-SHA256-framed messages. The relay is DP 1. (v3.5 is detected but not implemented.) ### IR blaster over Bluetooth ```bash uv run ir scan # find the blaster(s) uv run ir status # connect, pair, dump datapoints uv run ir learn # study mode: capture a code from a remote uv run ir send "8iOQERoC" # replay a captured code ``` `learn` puts the blaster in study mode; point the original remote at it and press a button. The captured code prints as base64 — Tuya's format of little-endian 16-bit microsecond pulse durations — and `send` replays it. DP defaults (send 201 as string, learn/study 202) are confirmed for the JMIR05 module; override them with `--send-dp`, `--send-dp-type`, `--learn-dp`, and `--study-dp` if your model differs. Unlike the global flags, these four go after the `learn` or `send` command — for example, `uv run ir send --send-dp 105 "8iOQERoC"`. ### Fingerbot through the Tuya gateway ```bash uv run tuya-re gateway status # dump sub-device status via the gateway uv run tuya-re gateway click # set click mode, then press ``` This path keeps Tuya's gateway in the loop: the Fingerbot is addressed as a sub-device (`cid` = the wizard's `node_id`) of the gateway, and tinytuya handles the LAN protocol. Status reads on BLE sub-devices often fail through the gateway even though writes work (tinytuya issue #677) — the click command prints the status but doesn't stop on it. If the handshake fails, try `version = "3.4"` first, then `"3.5"`, then `"3.3"`. ## How the pieces fit - `crypto`, `packet`, `protocol` — Tuya BLE v3: CRC16, MD5-derived login/session keys, AES-CBC frames, GATT chunking and reassembly. - `ble` — scanning, advertisement uuid decryption, connected sessions. - `devices` — per-product Fingerbot datapoint maps. - `lan_protocol`, `lan_session`, `lan_discovery` — Tuya local v3.3/v3.4: `0x000055AA` framing, session-key negotiation, UDP discovery. - `ir` — IR pulse codec and the learn/send flow. - `gateway` — tinytuya sub-device addressing for the gateway path. - `config`, `cli` — TOML credential loading and the argparse front end. The protocol layers are transport-agnostic and unit-tested against fakes that do real crypto, with framing pinned to byte-exact golden vectors from `tinytuya` and `ha_tuya_ble`. ## Develop ```bash uv run pytest # run the test suite uv run pytest --cov # with coverage uv run ruff check src tests # lint uv run ruff format --check src tests # formatting ``` `ble.py`, `lan_discovery.py`, and `cli.py` are hardware and entry-point boundaries, exercised against real devices rather than unit tests; they're excluded from coverage. ## Continuous integration Four GitHub Actions workflows run on every push and pull request to `main`: - **Lint** — ruff lint and format checks on Python 3.14. - **Codacy Coverage** — runs the tests with coverage and uploads the report to Codacy. Until the `CODACY_PROJECT_TOKEN` repository secret is set, the job still runs the tests and passes; it just skips the upload. - **Codacy Security Scan** — static analysis; results appear under **Security** > **Code scanning**. - **gitleaks** — scans the full git history for leaked secrets on every push. ## Troubleshooting | Symptom | Fix | |---|---| | `device ... not seen on UDP` (switch) | Make sure the plug is powered and on the same LAN, or set a static `ip` in `config.toml`. | | Switch set fails or decrypt error | The `local_key` is stale — re-pull it from the API Explorer. | | `device not found (N Tuya devices seen)` (BLE) | BLE advertising is intermittent; retry or raise `--timeout`. Power off the gateway and close the phone app — BLE allows one central. | | BLE command stops immediately on macOS | Bluetooth permission — run from a real terminal and approve the prompt. | | Gateway handshake fails or `Error 914` | Try `version = "3.4"`, then `"3.5"`, then `"3.3"`. | | Gateway status shows an error but clicks work | Expected for BLE sub-devices (tinytuya issue #677) — trust the write result. | ## Disclaimer This is an independent project for controlling devices you own. It isn't affiliated with or endorsed by Tuya.