- Python 98.5%
- Makefile 1.1%
- Shell 0.4%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| doc | ||
| tests | ||
| tuyactl | ||
| .gitignore | ||
| example.py | ||
| Makefile | ||
| README.md | ||
| run-tests.sh | ||
| tuya | ||
tuyactl
Control Tuya and Smart Life devices from the command line or from Python, directly over your WiFi or through the Tuya Cloud API.
tuya list
tuya on "WiFi Plug"
tuya brightness bedroom 40
tuya color bedroom warm
It uses only the Python 3 standard library, so you don't need to
pip install anything or set up a virtualenv.
| LAN | Cloud | |
|---|---|---|
| Typical command time | 0.2–0.4 s | 1–2 s |
| Works without internet | Yes | No |
| Works from anywhere | No, same network only | Yes |
| Needs the IoT Core subscription | Only for tuya local setup |
Always |
Contents
- Install
- Setup
- LAN control
- Commands
- Python library
- Configuration
- API expiry
- Troubleshooting
- Development
Install
make install # installs to ~/.local, no root needed
| Installed file | Location |
|---|---|
tuya command |
~/.local/bin/tuya |
| Python package | ~/.local/lib/tuyactl/ |
| Manual page | ~/.local/share/man/man1/tuya.1 |
To install somewhere else, set PREFIX. DESTDIR is supported for
packaging.
sudo make install PREFIX=/usr/local
make uninstall # use the PREFIX you installed with
Install and uninstall never touch your settings in ~/.config/tuyactl.
If man tuya doesn't find the page after installing, make install prints
the line to add to your shell profile:
export MANPATH="$HOME/.local/share/man:$MANPATH"
You can also run ./tuya from the repository without installing.
Setup
You need a free Tuya IoT Platform cloud project to get API credentials.
- Create an account on the Tuya IoT Platform.
- Go to Cloud → Development and create a cloud project. Choose the data centre where your phone app account is registered.
- Copy the Access ID and Access Secret from the project overview.
- On the project's Devices tab, choose Link App Account and scan the QR code with the Smart Life or Tuya app. This step is what makes your devices visible to the API.
- Make sure the IoT Core API service is authorised for the project.
Then run:
tuya init
tuya init asks for the credentials and region, checks them with Tuya,
lists the devices it finds and saves the settings. To control devices over
your WiFi as well, continue with LAN control.
Pick the right region. It must match where your app account was registered, which isn't necessarily where you live. If
initsays the Access ID is unknown, run it again and try another region.
| Region | Data centre |
|---|---|
us |
Western America |
us-e |
Eastern America |
eu |
Central Europe |
eu-w |
Western Europe |
cn |
China |
in |
India |
sg |
Singapore |
LAN control
Once your devices are set up for it, tuya sends commands straight to
them over your network. A one-time setup uses the cloud to fetch each
device's local key:
tuya local setup
NAME ADDRESS VERSION RESULT
WiFi Plug 2 192.168.0.190 3.3 ok
WiFi Plug 192.168.0.195 3.3 ok
tuya local setup does three things:
- It fetches each device's local key and data point numbers from the cloud.
- It listens on the network (for up to 20 seconds) to learn each device's address and protocol version.
- It tests each device with a status query.
From then on, commands to those devices go over the LAN:
tuya on "WiFi Plug" # WiFi Plug: ON (via lan 192.168.0.195 v3.3)
Modes
| Mode | Behaviour |
|---|---|
auto (default) |
LAN for devices set up for it, cloud for the rest. If a LAN command fails, it is retried through the cloud with a note. Devices set up for the LAN keep working when the internet is down. |
local |
LAN only. The cloud is never contacted, and no cloud credentials are needed. |
cloud |
Cloud only. |
tuya mode # show the mode
tuya mode local # set it
tuya --cloud status desk # override it for one command
tuya --local on desk
In local mode, tuya list checks each device directly, taking up to two
seconds in total, and shows whether it is online and switched on:
NAME STATE POWER VIA TYPE ID
Plug2 online off lan 192.168.0.190 cz a30482f1bdedda1d40vugr
Plug1 online on lan 192.168.0.195 cz a336e5e49e6e1437f54e3q
In the other modes, the online state comes from the cloud's device list.
If the cloud can't be reached, tuya list checks LAN devices directly, as
in local mode.
Managing LAN devices
tuya local show # addresses and versions
tuya local show --keys # include the local keys
tuya local scan # find devices, update addresses
tuya local set desk --ip 192.168.0.190 # set details by hand
tuya local set desk --version 3.4
tuya local remove desk # go back to the cloud
tuya local setup and tuya local scan listen for the announcements
devices broadcast every 5–20 seconds. Some broadcasts get lost over
WiFi, so if a device is missed, run tuya local scan or
tuya local set --ip.
Keeping LAN control working
- Same network: the computer must be on the same network as the devices.
- Fixed addresses: give each device a fixed address (a DHCP reservation
in your router). If an address changes,
tuya local scanpicks up the new one. - Re-pairing: removing a device from the app and adding it again gives
it a new local key, so run
tuya local setupagain afterwards. - Protocol versions: 3.1 to 3.5 are supported. Some 3.5 devices only announce themselves when asked, so set their address by hand.
- Hubs: devices behind a hub or gateway (such as Zigbee devices) aren't supported over the LAN.
- Connection limits: some devices accept only one or a few LAN connections at once, so other software holding connections open (such as Home Assistant) can get in the way.
- Key security: local keys give full control of a device. They're stored in the config file, which only you can read.
Data points on the LAN
Over the LAN, devices number their data points instead of naming them.
tuya translates using the numbers saved at setup, so tuya set plug switch_1 off works on both paths. tuya spec shows the numbers (#1),
and tuya set also accepts a number directly, such as tuya set plug 1 off.
tuya status shows data points missing from the device's published
specification by number. Colour values and a plug's power-on behaviour
(relay_status) are converted between the LAN and cloud formats. Other
enumerated values are sent as given, and a few devices use different names
on the LAN.
Commands
| Command | What it does |
|---|---|
tuya init [--region R] |
Set up or change credentials |
tuya list [--refresh] |
List devices (alias: ls) |
tuya status DEVICE |
Show the on/off state and every data point |
tuya on DEVICE / off DEVICE |
Switch a device on or off |
tuya toggle DEVICE |
Switch a device to the opposite state |
tuya brightness DEVICE 0-100 |
Set brightness (alias: dim) |
tuya temp DEVICE 0-100 |
Set white colour temperature (0 = warm, 100 = cool) |
tuya color DEVICE COLOUR |
Set an RGB colour (alias: colour) |
tuya set DEVICE CODE VALUE |
Write any data point |
tuya spec DEVICE |
Show what a device supports |
tuya alias [NAME [DEVICE]] [-d] |
List, add or delete short names |
tuya mode [auto|local|cloud] |
Show or set how devices are reached |
tuya local setup |
Prepare devices for LAN control |
tuya local scan |
Find devices on the network |
tuya local show [--keys] |
Show saved LAN details |
tuya local set DEVICE [--ip] [--version] [--key] |
Set LAN details by hand |
tuya local remove DEVICE |
Stop using the LAN for a device |
tuya refresh |
Fetch the device list again |
tuya raw GET|POST PATH [BODY] |
Call any Tuya API path |
The full reference, including exit codes, is in man tuya. The same page
is at doc/tuya.1; view it with make man.
Naming devices
A DEVICE argument can be any of these:
- an alias
- an exact name (not case-sensitive)
- part of a name
- a full device ID
- the start of a device ID
If a name matches more than one device, tuya lists the matches and does
nothing. Put quotes around names that contain spaces.
tuya alias desk "WiFi Plug 2"
tuya off desk
tuya alias desk --delete
Colours
tuya color accepts:
- hex:
#ff8800,ff8800or#f80 - decimal RGB:
255,136,0 - a name:
red,green,blue,white,warm,cool,yellow,orange,purple,pink,cyanormagenta
brightness, temp and color also switch the device on and put it in
the right mode (white or colour).
Controls without their own command
Each Tuya device exposes its controls as data points, and vendors choose
their own codes for them. tuya spec lists the codes a device accepts, and
tuya set writes any of them:
tuya spec "WiFi Plug"
tuya set plug switch_1 off
tuya set fan fan_speed_enum 3
tuya set lamp colour_data_v2 '{"h":240,"s":1000,"v":800}'
tuya set converts the value before sending it:
on,off,true,false,yesandnobecome booleans.- Numbers become numbers.
- Valid JSON starting with
{or[is sent as JSON. - Anything else is sent as a string.
Scripting
--json prints machine-readable output for list, status, spec,
local scan and local show. Put it before the command:
tuya --json status desk | jq .status
tuya --json list | jq -r '.[] | select(.online) | .name'
Exit status is 0 on success, 1 for errors (a Tuya error, an unknown
device or invalid input) and 2 for missing credentials or bad usage.
Python library
from tuyactl import connect
home = connect() # uses the same config and mode as the CLI
for device in home:
print(device.name, device.online, device.is_on(), device.capabilities())
lamp = home["bedroom"] # same name matching as the CLI
lamp.on()
lamp.set_brightness(40)
lamp.set_color("#ff8800")
lamp.set_color_temp(80)
lamp.set("work_mode", "scene") # any data point
print(lamp.status(refresh=True)) # {code: value}
print(lamp.get_brightness()) # percent
home.all_off() # every device that has a switch
home.close() # close LAN connections
connect(mode="local") or connect(mode="cloud") overrides the saved
mode. Home also works as a context manager (with connect() as home:).
After a call, device.via says how the device was reached.
You can also pass credentials directly, which always uses the cloud:
home = connect(client_id="...", secret="...", region="eu")
All errors are subclasses of tuyactl.TuyaError:
| Exception | Raised when |
|---|---|
ConfigError |
Credentials are missing or the config file is corrupt |
AuthError |
The region is invalid |
ApiError |
Tuya rejects a request (.code holds Tuya's error number) |
DeviceNotFound |
No device matches the name, or the device lacks the capability |
AmbiguousDevice |
The name matches more than one device |
tuyactl.local.LocalConnectionError |
A device can't be reached over the LAN |
tuyactl.local.LocalProtocolError |
A device's reply can't be decoded (usually a wrong local key) |
example.py is a short runnable example.
Configuration
| Path | Contents |
|---|---|
~/.config/tuyactl/config.json |
Access ID, secret, region, aliases, mode and LAN details (mode 600) |
~/.config/tuyactl/cache.json |
Access token and device list (safe to delete) |
The device list is cached for six hours. Device state is always read live.
Two optional settings can be added to config.json by hand:
local_timeout(top level): how many seconds to wait for a device. The default is 5.port(in a device's entry underlocal): the device's TCP port. The default is 6668.
Environment variables override the config file:
| Variable | Purpose |
|---|---|
TUYA_CLIENT_ID, TUYA_SECRET |
Credentials; with both set, no config file is needed |
TUYA_REGION |
Region |
TUYA_HOST, TUYA_SCHEME |
Send cloud requests to another server, such as a test mock |
TUYACTL_UDP_PORTS |
Comma-separated ports to listen on for devices (for testing) |
TUYACTL_PURE_AES |
Always use the built-in AES, even if cryptography is installed |
XDG_CONFIG_HOME |
Base directory for configuration (default ~/.config) |
API expiry
- Access ID and secret: these don't expire.
- Access token: this lasts about two hours.
tuyarenews it automatically. - IoT Core subscription: this does expire. New projects start on a free
trial. You can extend it at no cost in the Tuya IoT console under
Cloud → Cloud Services → IoT Core. When it lapses, cloud requests
fail until you renew it. Your devices keep working in the phone app, and
devices set up for LAN control keep working in
tuya. You only need the subscription again to runtuya local setup, for example after re-pairing a device. The service page shows your expiry date, so note it somewhere.
Troubleshooting
| Error | Likely cause |
|---|---|
[1004] sign invalid |
Wrong Access Secret, or your system clock is off |
[1005] / unknown client_id |
Wrong region; run tuya init again |
[1106] permission deny |
IoT Core isn't authorised, or the app account isn't linked |
[2001] or an empty tuya list |
No app account is linked to the project |
[28841101] |
The IoT Core subscription has expired |
No device matches ... |
The device was added or renamed; run tuya refresh |
... has no brightness control |
The device doesn't support it; check tuya spec |
note: LAN control of ... failed |
The device isn't reachable on the LAN, so the cloud was used; check the address with tuya local scan |
could not decrypt reply / closed the connection |
Wrong local key or protocol version; run tuya local setup |
not found on this network |
The device wasn't heard during setup; run tuya local scan --timeout 40 or tuya local set --ip |
cannot listen for devices |
Another program holds UDP ports 6666/6667/7000; set addresses with tuya local set --ip |
Development
make test # run the test suite
make man # preview the manual page
make clean # remove bytecode caches
The tests need no credentials and never touch real devices. They use two kinds of mock:
- Cloud:
tests/mock_tuya.pychecks request signatures with its own HMAC code, separate from the client's. - Devices:
tests/mock_device.pyplays the device side of protocols 3.1 to 3.5.
Some tests also check against outside references:
-
AES: the built-in AES is tested against the FIPS-197 and GCM specification test vectors.
-
tinytuya: if the
tinytuyalibrary is installed, the tests also check that it can talk to the mock devices and that the frame formats match. To run those:python3 -m venv .venv && .venv/bin/pip install tinytuya make test PYTHON=.venv/bin/python
| File | Role |
|---|---|
tuyactl/api.py |
Cloud request signing, token handling, HTTP |
tuyactl/local.py |
LAN protocol (3.1–3.5), discovery, value conversion |
tuyactl/crypto.py |
AES-ECB and AES-GCM in pure Python |
tuyactl/backends.py |
Cloud, LAN and automatic routing to devices |
tuyactl/devices.py |
Device model, capability detection, value scaling, name matching |
tuyactl/config.py |
Config file and cache |
tuyactl/cli.py |
Command-line interface |
tuyactl/errors.py |
Exceptions and error-code hints |
doc/tuya.1 |
Manual page |
tuya reads each device's published specification to choose which codes to
send. That's why on() works both on a bulb that uses switch_led and on a
plug that uses switch_1. Brightness and colour temperature are scaled to
the range each device reports, not to a fixed range.