Files
Udo 1b738c3782
Codacy Security Scan / Codacy static analysis (push) Has been cancelled
Codacy Coverage / Tests + coverage (push) Has been cancelled
gitleaks / scan (push) Has been cancelled
Lint / ruff (Python 3.14) (push) Has been cancelled
Improve named device and hold duration UX
2026-08-03 15:38:01 +02:00

199 lines
8.4 KiB
Markdown

# 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 <https://iot.tuya.com>, 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 <group> <command>`. 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.