List and control your Tuya (Smart Life / Tuya Smart) devices straight from your Ulanzi
keypad, using the Tuya Cloud API (the same one @tuyapi/cli uses in its cloud/wizard
commands). It does not depend on the device local IP, local key, or being on the same
network — everything goes through the cloud, so it works from anywhere.
The plugin ships 4 actions:
- Toggle Device — turns a device on/off (any
Booleanfunction, e.g.switch,switch_led,switch_1). The icon reflects the real state (green = on, grey = off) and the key shows the current temperature when the device is an air conditioner. - Send Command — sends an arbitrary
code/valuepair (scenes, brightness, temperature, modes, etc.). The value hint shows the expected type/range for the selected code. - Temperature + — raises the air conditioner setpoint by 1 degree per tap (respecting the device limit). The key displays the current temperature.
- Temperature - — lowers the setpoint by 1 degree per tap. The key displays the current temperature.
The main service polls the state of every configured device (every 30 s, plus an immediate
refresh after each action) and swaps the key icon for an SVG generated at runtime that
already embeds the temperature number (green = on, grey = off; orange arrow = up, blue arrow =
down). The icon is sent as a data URL via setBaseDataIcon. Because the number is part of the
bitmap itself, it renders reliably without depending on a textData/title that the host would
draw on top. Polling starts when the key appears (add) or when the page becomes visible
(setactive); on startup, settings are fetched via getSettings, so the status shows up
without needing a click.
On infrared air conditioners, the "temperature" shown is the setpoint (target
temperature): the IR hub has no ambient sensor and its status only exposes
power/mode/temp/wind. These devices report state under different codes than the control
ones (power/temp in status vs switch/temp in functions); the plugin resolves that
divergence automatically.
Each action (Toggle, Temperature +, Temperature -) has a "Show temperature on this button" option in the Property Inspector (on by default). Turn it off on buttons where the temperature should not appear — for example, on a Toggle button you labeled with a manual Title (e.g. VRF 01/02/03), to avoid the title colliding with the number. With the option off, the icon shows only the state (power) or the arrow, centered.
Multiple buttons can control the same device (Toggle + Temperature ±). Polling is unified per device, and any change (click or poll) repaints all buttons of that device at once — right after a click there is an optimistic broadcast plus a confirmation refresh, so no button ends up out of sync.
Device listing resolves the entire account (all homes): it obtains the uid of a device —
either from the "Seed Device ID" field or discovered automatically via
/v1.0/iot-01/associated-users/devices / /v2.0/cloud/thing/device — and then calls
/v1.0/users/{uid}/devices + /v1.0/users/{uid}/homes. This is required because the fallback
endpoints alone only return the home directly linked to the project (not the others). Homes
where you are ADMIN (not OWNER) show up normally — the listing uses users/{uid}/devices and
filters by owner_id, working around the per-home endpoint permission deny.
The plugin writes events to ~/tuya-ulanzi-debug.log for troubleshooting.
The Property Inspector, action names and store description are localized. Included locale files:
| Locale | Language |
|---|---|
en |
English |
pt_PT |
Portuguese |
Localization follows the Ulanzi i18n convention (Name/Description/Actions at the top for
the store, and a Localization object for Property Inspector strings marked with
data-localize). Additional locales can be added by dropping a {locale}.json file in the
package root.
All settings live in the Property Inspector. Tuya Cloud credentials are stored globally (entered once, shared by every action); the device selection is per-button.
Credentials (global, all actions):
| Field | Description |
|---|---|
| Access ID | Project Access ID (Tuya IoT Platform → Overview) |
| Access Secret | Project Access Secret |
| Data Center | us (Americas) · eu (Europe) · cn (Asia/China) · in (India) |
| Seed Device ID | Optional — any device ID from the app; auto-discovered if left blank |
Device selection (per button):
| Field | Actions | Description |
|---|---|---|
| Home | all | Filter by home; "All homes" shows everything |
| Device | all | The target Tuya device |
| Function (switch) | Toggle | The boolean function to toggle |
| Function (code) + Value | Send Command | The code and the value to send |
| Show temperature | Toggle, Temp +, Temp - | Toggle the temperature number on the icon (on by default) |
Temperature ± actions auto-detect the temperature function — you only pick the home and device.
- Go to https://iot.tuya.com → Cloud → Development → your project.
- Copy Access ID and Access Secret (Overview tab).
- Devices → Link Tuya App Account → link your Smart Life / Tuya Smart account (scan the QR from the app, under the Me menu).
- Note the project Data Center (us / eu / cn / in).
- (Optional) A Device ID from any device works as a "seed" for listing. Usually not needed: the plugin discovers a device on its own to resolve the account.
Requires Node 20+ for the main service (Ulanzi Studio 3.x bundles the Node runtime).
Option A — folder: copy the com.ulanzi.tuya.ulanziPlugin/ folder (with node_modules)
into the Ulanzi Studio plugins folder and restart the app.
Option B — package: from inside com.ulanzi.tuya.ulanziPlugin/, run python3 package.py
to generate ../dist/…_v1.1.6.zip (already including node_modules) and install the file from
Ulanzi Studio.
- Drag Toggle Device onto a key.
- In the Property Inspector: fill in Access ID, Access Secret, Data Center and (recommended) the Seed Device ID. Credentials are saved globally (only once).
- Click Load devices. If the account has more than one home, use the Home selector to filter; "All homes" shows everything.
- Pick the device → pick the switch function.
- Press the key to toggle. For Send Command, pick the device, the
code, and type thevalue(the hint shows the type/range). Infrared ACs exposeswitch(on/off on Toggle) andmode/temp/fan(use Send Command).
- Toggle Device with the AC and the
switchfunction → on/off; the key shows on/off + the current temperature. - Temperature + and Temperature - with the same AC → adjust 1 degree per tap; just select the home and device (the temperature function is detected automatically). Both keys show the current temperature.
The plugin package lives in the com.ulanzi.tuya.ulanziPlugin/ subfolder (the layout the
Ulanzi Community Store expects); store.json and resources/ sit at the repo root.
com.ulanzi.tuya.ulanziPlugin/ -> the installable plugin package
manifest.json -> 4 actions (toggle, command, tempup, tempdown); Node main service
plugin/app.js -> routes host events (add/run/setactive/clear/sendToPlugin)
plugin/tuya-cloud.js -> Tuya Cloud wrapper (@tuya/tuya-connector-nodejs) + climate
plugin/climate-hub.js -> unified per-device polling; broadcast/refresh to all buttons
plugin/icon.js -> generates the SVG (temperature/state, with or without number) as a data URL
plugin/logger.js -> diagnostic log at ~/tuya-ulanzi-debug.log
plugin/actions/*.js -> per-action logic (cache by context); reads showTemp from settings
property-inspector/* -> configuration UI (credentials, device/code/home, show temperature)
Run from inside com.ulanzi.tuya.ulanziPlugin/:
npm run test:mock # validates the main service protocol (no credentials)
npm run test:tuya # validates against Tuya Cloud (needs the env vars below)Variables for test:tuya:
| Variable | Required | Description |
|---|---|---|
TUYA_ACCESS_ID |
yes | Project Access ID |
TUYA_ACCESS_SECRET |
yes | Project Access Secret |
TUYA_REGION |
yes | us / eu / cn / in |
TUYA_SEED_DEVICE_ID |
recommended | A device ID from the app (improves listing) |
TUYA_TEST_DEVICE_ID |
no | Forces the target device (otherwise uses the first) |
TUYA_TEST_CODE |
no | Forces the switch code (otherwise auto-detected) |
TUYA_DO_TOGGLE |
no | 1 runs a real toggle + restore |
MIT