homebridge-tuya-plus 3.14.0-dev.5 → 3.14.0-dev.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/.github/workflows/publish-dev.yml +298 -31
  2. package/AGENTS.md +162 -0
  3. package/CLAUDE.md +1 -0
  4. package/Changelog.md +16 -3
  5. package/Readme.MD +8 -32
  6. package/config.schema.json +79 -79
  7. package/index.js +599 -358
  8. package/lib/AirPurifierAccessory.js +1 -1
  9. package/lib/BaseAccessory.js +54 -17
  10. package/lib/ConvectorAccessory.js +1 -1
  11. package/lib/CustomMultiOutletAccessory.js +2 -1
  12. package/lib/DoorbellAccessory.js +9 -16
  13. package/lib/GarageDoorAccessory.js +1 -1
  14. package/lib/IrrigationSystemAccessory.js +260 -120
  15. package/lib/MultiOutletAccessory.js +3 -2
  16. package/lib/OilDiffuserAccessory.js +10 -8
  17. package/lib/RGBTWLightAccessory.js +13 -11
  18. package/lib/RGBTWOutletAccessory.js +11 -9
  19. package/lib/SimpleBlindsAccessory.js +5 -5
  20. package/lib/SimpleDimmer2Accessory.js +1 -1
  21. package/lib/SimpleDimmerAccessory.js +10 -6
  22. package/lib/SimpleGarageDoorAccessory.js +114 -25
  23. package/lib/SimpleHeaterAccessory.js +4 -4
  24. package/lib/SimpleLightAccessory.js +1 -1
  25. package/lib/SwitchAccessory.js +3 -2
  26. package/lib/TWLightAccessory.js +2 -2
  27. package/lib/TuyaAccessory.js +75 -42
  28. package/lib/TuyaCloudApi.js +121 -8
  29. package/lib/TuyaCloudDevice.js +434 -31
  30. package/lib/TuyaCloudMessaging.js +34 -41
  31. package/lib/TuyaDevice.js +269 -0
  32. package/lib/TuyaDiscovery.js +25 -9
  33. package/lib/ValveAccessory.js +16 -12
  34. package/lib/VerticalBlindsWithTilt.js +27 -25
  35. package/lib/WledDimmerAccessory.js +46 -81
  36. package/package.json +5 -6
  37. package/scripts/cleanup-pr-tags.js +122 -0
  38. package/test/BaseAccessory.test.js +94 -15
  39. package/test/IrrigationSystemAccessory.test.js +293 -67
  40. package/test/MultiOutletAccessory.test.js +18 -3
  41. package/test/RGBTWLightAccessory.test.js +3 -3
  42. package/test/SimpleFanLightAccessory.test.js +3 -3
  43. package/test/SimpleGarageDoorAccessory.test.js +146 -19
  44. package/test/TuyaAccessory.protocol.test.js +76 -3
  45. package/test/TuyaCloudApi.test.js +110 -1
  46. package/test/TuyaCloudDevice.test.js +564 -2
  47. package/test/TuyaCloudMessaging.test.js +24 -5
  48. package/test/TuyaDevice.test.js +266 -0
  49. package/test/getCategory.test.js +1 -0
  50. package/test/index.test.js +271 -0
  51. package/test/support/mocks.js +13 -0
  52. package/wiki/Supported-Device-Types.md +64 -34
  53. package/wiki/Tuya-Cloud-Setup.md +31 -108
@@ -15,7 +15,8 @@ If you are looking for verified configurations for your specific device, please
15
15
  |Barely Smart Power Strip|`Outlet`|Smart power strips that don't allow individual control of the outlets|
16
16
  |Air Conditioner|`AirConditioner`<sup>[6](#air-conditioners)</sup>|Cooling and heating devices <small>([instructions](#air-conditioners))</small>|
17
17
  |Heat Convector|`Convector`<sup>[7](#heat-convectors)</sup>|Heating panels <small>([instructions](#heat-convectors))</small>|
18
- |WLED Dimmer|`WledDimmer` (or legacy `SimpleDimmer`)<sup>[8](#simple-dimmers)</sup>|Dimmer switches with power control, with optional WLED sync <small>([instructions](#simple-dimmers))</small>|
18
+ |Simple Dimmer|`SimpleDimmer`<sup>[8](#simple-dimmers)</sup>|Dimmer switches with power control <small>([instructions](#simple-dimmers))</small>|
19
+ |WLED Dimmer|`WledDimmer`<sup>[8](#simple-dimmers)</sup>|Dimmer switches with power control, plus optional WLED brightness sync and preset-effect switches <small>([instructions](#simple-dimmers))</small>|
19
20
  |Simple Heater|`SimpleHeater`<sup>[9](#simple-heaters)</sup>|Heating solutions with only temperature control <small>([instructions](#simple-heaters))</small>|
20
21
  |Garage Door|`GarageDoor`<sup>[10](#garage-doors)</sup>|Smart garage doors or garage door openers <small>([instructions](#garage-doors))</small>|
21
22
  |Simple Garage Door|`SimpleGarageDoor`<sup>[10](#simple-garage-doors)</sup>|Sliding gate openers and garage door controllers with open/stop/close action DPs and a simple three-value status DP <small>([instructions](#simple-garage-doors))</small>|
@@ -223,7 +224,17 @@ Some smart power strips don't have sequential data-points. Using `CustomMultiOut
223
224
  ```
224
225
 
225
226
  ### Air Conditioners
226
- These devices have cooling and/or heating capabilities; they could also have _dry_, _fan_, or others modes but HomeKit's definition doesn't facilitate modes other than _heat_, _cool_, and _auto_. By default, _heat_ and _cool_ modes are enabled; to let the plugin know that a device doesn't have heating or cooling capabilities, add an additional parameter named `noHeat` or `noCool` and set it to `true`. Tuya devices don't follow a unified pattern for naming the modes, for example cooling mode is called _COOL_ on Kogan's KAPRA14WFGA and _cold_ on Igenix's IG9901WIFI; by default, the plugin uses the phrases _COOL_ and _HEAT_ while communicating with your device but to let the plugin know that a device has different phrases, add additional parameters using `cmdHeat` and `cmdCool`. Additional parameters can be found in the sample below.
227
+ These devices have cooling and/or heating capabilities; they could also have _dry_, _fan_, or others modes but HomeKit's definition doesn't facilitate modes other than _heat_, _cool_, and _auto_. By default, _heat_, _cool_ and _auto_ modes are enabled; to let the plugin know that a device doesn't have heating, cooling or auto capabilities, add an additional parameter named `noHeat`, `noCool` or `noAuto` and set it to `true`.
228
+
229
+ Tuya devices don't follow a unified pattern for naming the modes, for example cooling mode is called _COOL_ on Kogan's KAPRA14WFGA but _cold_ on Igenix's IG9901WIFI and most "standard" Tuya AC (category `kt`) firmwares. The phrases are also **case-sensitive**. By default, the plugin uses the phrases _COOL_, _HEAT_ and _AUTO_ while communicating with your device; if your device uses different phrases (a very common case — many ACs report lowercase `cold` / `hot` / `auto` / `wet` / `wind`), override them with `cmdCool`, `cmdHeat` and `cmdAuto`. If the modes don't switch at all, this is almost always the reason.
230
+
231
+ > **Tip — finding the exact phrases.** The phrases are the raw values of the `mode` data-point (DP `4`). You can read them from the [Tuya IoT Platform](https://iot.tuya.com) → _Cloud → API Explorer → Query Things Data Model_ (or _Get Device Specification_) for your device. For example, a device whose `mode` enum range is `["auto","cold","wet","wind","hot"]` needs `cmdCool: "cold"`, `cmdHeat: "hot"`, `cmdAuto: "auto"`.
232
+
233
+ The fan speed data-point (DP `5`, `windspeed`) is usually an **enum of string values** like `"1"`, `"2"`, `"3"`. Set `fanSpeedSteps` to the number of speeds (e.g. `3`); this both maps HomeKit's 0–100 % slider onto the right number of steps and — importantly — sends the speed as a string, which these firmwares require. Without it the fan may silently ignore speed changes.
234
+
235
+ Many ACs have no child-lock data-point; if yours doesn't, set `noChildLock: true` so the plugin doesn't add a (non-functional) lock control. Likewise, set `noRotationSpeed: true` if the device has no fan-speed control.
236
+
237
+ Additional parameters can be found in the sample below.
227
238
 
228
239
  ```json5
229
240
  {
@@ -242,12 +253,27 @@ These devices have cooling and/or heating capabilities; they could also have _dr
242
253
  /* This device has no heating function */
243
254
  "noHeat": true ,
244
255
 
245
- /* Override cooling phrase */
256
+ /* This device has no auto function */
257
+ "noAuto": true,
258
+
259
+ /* Override cooling phrase (case-sensitive; e.g. "cold" on many devices) */
246
260
  "cmdCool": "COOL",
247
261
 
248
- /* Override heating phrase */
262
+ /* Override heating phrase (case-sensitive; e.g. "hot" on many devices) */
249
263
  "cmdHeat": "HEAT",
250
264
 
265
+ /* Override auto phrase (case-sensitive; e.g. "auto" on many devices) */
266
+ "cmdAuto": "AUTO",
267
+
268
+ /* Number of fan speeds; also sends the speed as a string (required by most ACs) */
269
+ "fanSpeedSteps": 3,
270
+
271
+ /* This device has no fan-speed control */
272
+ "noRotationSpeed": true,
273
+
274
+ /* This device has no child-lock data-point */
275
+ "noChildLock": true,
276
+
251
277
  /* This device has no oscillation (swinging) function */
252
278
  "noSwing": true,
253
279
 
@@ -258,7 +284,10 @@ These devices have cooling and/or heating capabilities; they could also have _dr
258
284
  "maxTemperature": 40,
259
285
 
260
286
  /* Temperature change steps, in Celsius (°C) */
261
- "minTemperatureSteps": 1
287
+ "minTemperatureSteps": 1,
288
+
289
+ /* Only if your firmware reports/accepts temperatures scaled by 10 (e.g. 170 = 17.0 °C) */
290
+ "temperatureDivisor": 10
262
291
  }
263
292
  ```
264
293
 
@@ -317,14 +346,15 @@ If your signature doesn't have a variation of _low_ or _high_, `SimpleHeater` wo
317
346
  ```
318
347
 
319
348
  ### Simple Dimmers / WLED Dimmers
320
- These are switches that allow turning on and off, and dimming. Use type `WledDimmer` (legacy `SimpleDimmer` alias is also supported for backward compatibility).
349
+ These are switches that allow turning on and off, and dimming. Two distinct types are available:
321
350
 
322
- When using with a WLED controller (e.g. a Tuya-based relay/dimmer feeding power to a WLED strip), you can use advanced sync options:
351
+ - `SimpleDimmer` a plain dimmer with power and brightness control.
352
+ - `WledDimmer` — a dimmer that can additionally drive a [WLED](https://kno.wled.ge/) controller (e.g. a Tuya-based relay/dimmer feeding power to a WLED strip). With none of the WLED options below configured, it behaves exactly like a `SimpleDimmer`.
323
353
 
324
- - `syncBrightnessToWled`: set to WLED device IP (e.g. "192.168.1.50" or "192.168.1.50:80") to sync HomeKit brightness changes directly to WLED over HTTP, keeping the Tuya dimmer at 100%.
325
- - `presetEffects`: array of effect configs to expose as switches in HomeKit (each turns on a WLED fx preset, optionally with staticColor).
354
+ The following options apply to `WledDimmer` only (they are ignored by `SimpleDimmer`):
326
355
 
327
- See the accessory source for full details on WLED integration.
356
+ - `syncBrightnessToWled`: set to the WLED device IP (e.g. "192.168.1.50" or "192.168.1.50:80") to sync HomeKit brightness changes directly to WLED over HTTP, keeping the Tuya dimmer at 100%. This talks to the WLED controller directly on your **LAN**, independently of how the Tuya dimmer is reached — so if the Tuya dimmer is ever on the cloud fallback (because the LAN is down), the WLED sync is best-effort and may not go through until the LAN is back.
357
+ - `presetEffects`: array of effect configs to expose as switches in HomeKit (each turns on a WLED fx preset, optionally with staticColor).
328
358
 
329
359
  ```json5
330
360
  {
@@ -470,9 +500,10 @@ external stop, where close is accepted immediately), a close is sent as
470
500
  /* Optional. If set, exposes an extra stateful switch that mirrors
471
501
  whether the gate is currently open in HomeKit's view. Tapping it
472
502
  ON triggers a partial-open: the gate opens and then stops itself
473
- this many milliseconds later, leaving the gate partially open.
474
- Tapping it OFF triggers a standard full close. Useful for letting
475
- someone pass through briefly. Leave unset to skip the switch. */
503
+ this many milliseconds after it actually starts moving, leaving the
504
+ gate partially open. Tapping it OFF triggers a standard full close.
505
+ Useful for letting someone pass through briefly. Leave unset to skip
506
+ the switch. */
476
507
  "partialOpenMs": 2000,
477
508
 
478
509
  /* Optional. Exposes extra Force Open and Force Close momentary
@@ -723,12 +754,11 @@ If the light, brightness and turning the fan **off** all work, but turning the f
723
754
 
724
755
  ### Irrigation Systems / Sprinklers
725
756
 
726
- Multi-valve Tuya irrigation/sprinkler controllers (the battery-powered Wi-Fi "faucet timers" that expose several `switch_*` valves, a `battery_percentage` and a `rain_sensor_state`) are exposed as a single, fully-fledged HomeKit **Irrigation System** accessory:
757
+ Multi-valve Tuya irrigation/sprinkler controllers (the battery-powered Wi-Fi "faucet timers" that expose several `switch_*` valves and a `battery_percentage`) are exposed as a single, fully-fledged HomeKit **Irrigation System** accessory:
727
758
 
728
759
  * one **Irrigation System** tile that contains every zone,
729
760
  * one **Valve** per zone (`ValveType = Irrigation`) — each with its own on/off, its own **Duration** picker and a live countdown,
730
- * an optional **Battery** service (level, low-battery warning, and — for solar/USB-C rechargeable units that report it — live charging status),
731
- * an optional **rain sensor** (a Contact sensor by default).
761
+ * an optional **Battery** service (level, low-battery warning, and — for solar/USB-C rechargeable units that report it — live charging status).
732
762
 
733
763
  Because these devices are slow to respond, all zone changes that happen close together — turning the whole system on/off, or running a scene that toggles several zones — are merged into a **single** Tuya command instead of a burst of them.
734
764
 
@@ -741,14 +771,13 @@ Because these devices are slow to respond, all zone changes that happen close to
741
771
  "type": "IrrigationSystem",
742
772
  "id": "bfae6739xxxxxxxxxxxxxx", // the cloud Device ID
743
773
  "cloud": true,
744
- "valveCount": 4,
745
- "noRainSensor": true // many battery timers have no rain sensor
774
+ "valveCount": 4
746
775
  }
747
776
  ```
748
777
 
749
778
  #### Minimal Configuration
750
779
 
751
- The defaults match the common 4-zone layout (valves A–D on data-points `1`–`4`, battery on `46`, rain on `49`):
780
+ The defaults match the common 4-zone layout (valves A–D on data-points `1`–`4`, battery on `46`):
752
781
 
753
782
  ```json5
754
783
  {
@@ -763,19 +792,21 @@ The defaults match the common 4-zone layout (valves A–D on data-points `1`–`
763
792
 
764
793
  #### Per-zone timers and "indefinite" mode
765
794
 
766
- Each zone has its own **Duration**. When a zone is switched on it runs for that duration and the plugin closes it automatically (HomeKit's countdown is display-only — the plugin always enforces the shut-off, even after a Homebridge restart).
795
+ Each zone has its own **Duration**. When a zone is switched on it runs for that duration and is then closed automatically. Two mechanisms enforce this together:
767
796
 
768
- * Set a zone's duration to **`0`** to make it run **indefinitely** (until it is switched off again) handy for long, manual watering tasks.
797
+ * a **software timer** in the plugin drives HomeKit's live countdown and the precise (sub-minute) shut-off while Homebridge is connected — it also closes a zone that was switched on at the device or that was already running when Homebridge restarted; and
798
+ * the device's **own countdown timer**, when the controller exposes one (`countdown_1..n`, DP `17..` by default). When a zone is switched **on**, its run length (whole minutes) is sent to the hardware countdown in the *same command* as the switch, so the valve still closes on schedule **even if Homebridge or the network drops out while it's running** — the hardware closes itself. This is on by default whenever the device reports these data-points; set `nativeCountdown: false` to fall back to the software timer alone.
799
+
800
+ > On these controllers the countdown is **not** a passive setting — *writing it starts a run and opens the zone*. So the plugin only ever sets it while switching a zone on (or for a zone that's already running); it is **never** written on connect or while a zone is off. The two mechanisms are belt-and-suspenders: the hardware countdown handles the offline case, and the software timer still switches the zone off after its Duration if the hardware countdown is unreliable. A zone switched on at the device with no usable countdown is given one (so it still auto-closes offline); a valid countdown the device set itself (e.g. a duration chosen in the Tuya app) is left alone.
801
+
802
+ * Set a zone's duration to **`0`** to make it run **indefinitely** (until it is switched off again) — handy for long, manual watering tasks. (No finite hardware countdown is sent, so the device won't auto-close it either.)
803
+ * The hardware countdown is whole **minutes**, capped by the device at **120 min**; HomeKit's Duration is in seconds. The value written is bounded by `maxDuration` (default `7200`s = 120 min) — lower `maxDuration` to e.g. `3600` to cap zone runs at 60 minutes.
769
804
  * Apple's Home app only offers duration presets up to **1 hour**. For longer runs (up to `maxDuration`) or to preset "indefinite" zones, set `defaultDuration` / the per-valve `defaultDuration` in the config, or use the Eve app.
770
805
 
771
806
  #### Master ("toggle all") switch
772
807
 
773
808
  Switching the whole Irrigation System tile **off** closes every open zone, and switching it **on** opens every zone — each as one combined command (mirroring the physical "all" button many of these controllers have). Either direction can be disabled with `masterTurnsOffAllZones` / `masterTurnsOnAllZones`.
774
809
 
775
- #### Rain sensor: Contact vs Leak
776
-
777
- By default the rain sensor is a **Contact sensor** (`raining → Open`). You can switch it to a **Leak sensor** (`raining → Leak Detected`) with `"rainSensorType": "leak"` — leak sensors read more naturally for rain and make "when it starts raining" automations obvious, **but HomeKit treats them as safety accessories and raises critical alerts that bypass Do-Not-Disturb on every rainfall**, which many people find too noisy. Use `rainInverted` if the wet/dry states appear reversed.
778
-
779
810
  #### Full Configuration
780
811
 
781
812
  ```json5
@@ -791,17 +822,23 @@ By default the rain sensor is a **Contact sensor** (`raining → Open`). You can
791
822
  /* Simple: number of valves on sequential data-points 1, 2, 3, … */
792
823
  "valveCount": 4,
793
824
  /* …or, for custom names / non-sequential data-points, define them explicitly
794
- (this overrides valveCount). defaultDuration is in seconds; 0 = indefinite. */
825
+ (this overrides valveCount). defaultDuration is in seconds; 0 = indefinite.
826
+ dpCountdown is the device's built-in auto-off timer for the zone; it
827
+ defaults to the switch dp + 16 (so switch 1 → countdown 17) and only needs
828
+ setting for code-addressed or non-standard zones. */
795
829
  "valves": [
796
830
  { "name": "Front Lawn", "dp": 1, "defaultDuration": 900 },
797
831
  { "name": "Back Lawn", "dp": 2, "defaultDuration": 900 },
798
832
  { "name": "Flower Beds","dp": 3, "defaultDuration": 600 },
799
- { "name": "Drip Line", "dp": 4, "defaultDuration": 0 }
833
+ { "name": "Drip Line", "dp": 4, "defaultDuration": 0, "dpCountdown": 20 }
800
834
  ],
801
835
 
802
836
  /* --- Timers --- */
803
837
  "defaultDuration": 600, /* default per-zone run time, seconds (0 = indefinite) */
804
838
  "maxDuration": 7200, /* upper bound advertised to HomeKit, seconds */
839
+ "nativeCountdown": true, /* mirror durations to the device's own countdown timer
840
+ (countdown_1.., DP 17..) so zones still auto-close
841
+ offline; set false for software-timer-only */
805
842
 
806
843
  /* --- Master switch behaviour --- */
807
844
  "masterTurnsOnAllZones": true,
@@ -813,12 +850,5 @@ By default the rain sensor is a **Contact sensor** (`raining → Open`). You can
813
850
  "lowBatteryThreshold": 20,
814
851
  "dpCharging": 101, /* boolean charging-status DP (solar / USB-C); omit if not reported */
815
852
  /* "noBattery": true, */
816
-
817
- /* --- Rain sensor (omit / set noRainSensor:true if not present) --- */
818
- "dpRain": 49,
819
- "rainSensorType": "contact", /* or "leak" */
820
- "rainOnValue": "rain", /* enum value reported while raining */
821
- "rainInverted": false
822
- /* "noRainSensor": true, */
823
853
  }
824
854
  ```
@@ -1,13 +1,12 @@
1
1
  # Tuya Cloud Setup
2
2
 
3
- This plugin is **LAN-first** — almost every Tuya device is controlled locally. But a few devices **cannot** be reached over the LAN at all:
3
+ This plugin is **LAN-first** — every Tuya device is controlled locally whenever it can be. Adding your Tuya Cloud credentials turns the cloud into a **transparent fallback for every device**: each accessory tries the LAN first, and only falls back to the cloud when the device can't be reached locally. It's **opt-in** (nothing happens unless you add credentials) and **local stays the preferred path**.
4
4
 
5
- > Battery-powered **"sleepy"** devices — most notably multi-zone **irrigation / faucet timers** — sleep almost all the time to save battery and only ever connect *outbound* to Tuya's cloud (over MQTT) for a brief moment when they wake. They never keep the local port open and never answer LAN discovery, so the local protocol can't reach them. (Tuya's own developer docs state LAN control is unavailable in low-power mode.)
5
+ It helps in two situations:
6
6
 
7
- For these devices the plugin can talk to the **Tuya Cloud** instead. It is **opt-in, per device** your other devices stay 100% local.
7
+ > **Devices that are never on the LAN.** Some devices, most notably some battery-powered **"sleepy"** ones, never keep the local port open and never answer LAN discovery, so the local protocol can't reach them. (Tuya's own developer docs state LAN control is unavailable in low-power mode.)
8
8
 
9
- * Initial state + control go through the Tuya OpenAPI (signed HTTPS).
10
- * Live updates (including physical button presses) arrive over Tuya's **MQTT** message service — no polling.
9
+ > **Devices that occasionally drop off the LAN.** If a normally-local device sometimes shows "No Response" in HomeKit, the cloud fallback quietly covers those moments.
11
10
 
12
11
  ---
13
12
 
@@ -21,32 +20,33 @@ For these devices the plugin can talk to the **Tuya Cloud** instead. It is **opt
21
20
 
22
21
  ## 2. Authorize the required API services
23
22
 
24
- In the project, go to **Service API → Go to Authorize** and make sure these are subscribed (all free):
25
-
26
- * **IoT Core**
27
- * **Authorization**
28
- * **Smart Home Basic Service**
29
- * **Device Status Notification** ← needed for realtime MQTT updates
30
-
31
- > ⚠️ The free trial of *IoT Core* lasts ~6 months, after which you must click **Extend Trial Period** (Cloud → My Services). If it lapses you'll see *"No permissions. Your subscription … has expired."*
23
+ There are two types of projects: `Custom` and `Smart Home`. The difference between them is:
32
24
 
33
- ## 3. Link your app account (so the project can see your devices)
25
+ * The `Custom` project pulls devices from the project's assets.
26
+ * The `Smart Home` project pulls devices from the user's home in the Tuya app.
34
27
 
35
- Still in the project: **Devices Link App Account Add App Account**, then in the **Tuya Smart / Smart Life** app go to **Me → ⊞ (scan icon, top-right)** and scan the QR code. Your irrigation timer should now appear under **Devices → All Devices**.
28
+ If you are a personal user and are unsure which one to choose, please use the Smart Home project.
36
29
 
37
- ## 4. Find the device ID
30
+ In the project, go to **Service API → Go to Authorize** and make sure these are subscribed (all free):
38
31
 
39
- **Devices All Devices** → click your device → copy its **Device ID** (a string like `bfae6739…tfx`). (You can also find it in the Smart Life app: device → ✎/⚙ → *Device Information*.)
32
+ * Authorization Token Management
33
+ * Device Status Notification
34
+ * IoT Core
35
+ * IoT Video Live Stream (for cameras)
36
+ * Industry Project Client Service (for the `Custom` project)
37
+ * IR Control Hub Open Service (for IR devices)
38
+ * Smart Home Scene Linkage (for scenes)
39
+ * Smart Lock Open Service (for Lock devices)
40
40
 
41
- ---
41
+ > **⚠️Remember to extend the API trial period every 6 months here: [Tuya IoT Platform > Cloud > Cloud Services > IoT Core](https://iot.tuya.com/cloud/products/detail?abilityId=1442730014117204014&id=p1668587814138nv4h3n&abilityAuth=0&tab=1) (the first-time subscription only gives you 1 month).**
42
42
 
43
- ## 5. Configure the plugin
43
+ ## 3. Link your app account (so the project can see your devices)
44
44
 
45
- Add a **top-level `cloud` block** with your credentials, and set **`"cloud": true`** on the device. There are two project styles:
45
+ Still in the project: **Devices → Link App Account → Add App Account**, then in the **Tuya Smart / Smart Life** app go to **Me (scan icon, top-right)** and scan the QR code. Your irrigation timer should now appear under **Devices → All Devices**.
46
46
 
47
- ### Smart Home project (recommended — what most people have)
47
+ ## 4. Configure the plugin
48
48
 
49
- Authenticates as your app account (username/password), so it sees exactly the devices linked in step 3.
49
+ Add a **top-level `cloud` block** with your credentials:
50
50
 
51
51
  ```json5
52
52
  {
@@ -61,100 +61,23 @@ Authenticates as your app account (username/password), so it sees exactly the de
61
61
  "schema": "tuyaSmart" // or "smartlife" if you use the Smart Life app
62
62
  },
63
63
  "devices": [
64
- {
65
- "name": "Garden Irrigation",
66
- "type": "IrrigationSystem",
67
- "id": "bfae6739xxxxxxxxxxxxxx", // the cloud Device ID
68
- "cloud": true,
69
- "valveCount": 4,
70
- "noRainSensor": true // most battery timers have no rain DP
71
- }
64
+ // ...
72
65
  ]
73
66
  }
74
67
  ```
75
68
 
76
- ### Custom project
77
-
78
- If you created a **Custom** project (devices linked by QR to the project's asset) just omit `username`/`password`/`countryCode`/`schema`:
79
-
80
- ```json5
81
- "cloud": {
82
- "accessId": "your-access-id",
83
- "accessKey": "your-access-secret",
84
- "region": "eu"
85
- }
86
- ```
87
-
88
- ### Per-device credentials (optional)
89
-
90
- Instead of (or in addition to) the platform block, a device can carry its own credentials — handy if different devices live in different Tuya projects:
91
-
92
- ```json5
93
- {
94
- "name": "Garden Irrigation",
95
- "type": "IrrigationSystem",
96
- "id": "bfae6739xxxxxxxxxxxxxx",
97
- "cloud": {
98
- "accessId": "…",
99
- "accessKey": "…",
100
- "region": "eu",
101
- "username": "…",
102
- "password": "…",
103
- "countryCode": "48"
104
- }
105
- }
106
- ```
107
-
108
- > No local `key` is needed for cloud devices — the cloud authenticates with your project credentials.
109
-
110
- ---
111
-
112
- ## Data-points are addressed by "code" on the cloud
113
-
114
- Over the LAN, data-points are numbered (1, 2, …). Over the **cloud** they're named **codes** (e.g. `switch_1`, `battery_percentage`). When a cloud device connects, the plugin **logs the exact codes** it reports, e.g.:
115
-
116
- ```
117
- Garden Irrigation: Tuya Cloud data-point codes → switch_1=false, switch_2=false, switch_3=false, switch_4=false, countdown_1=0, …, battery_percentage=99
118
- ```
119
-
120
- The `IrrigationSystem` defaults already match the common 4-zone layout (`switch_1`…`switch_4`, battery `battery_percentage`). If your device differs, use the logged codes:
121
-
122
- ```json5
123
- "valves": [
124
- { "name": "Front Lawn", "dp": "switch_1", "defaultDuration": 900 },
125
- { "name": "Back Lawn", "dp": "switch_2", "defaultDuration": 900 }
126
- ],
127
- "dpBattery": "battery_percentage",
128
- "noRainSensor": true // or: "dpRain": "rain_sensor_state"
129
- ```
130
-
131
- See **[Irrigation Systems / Sprinklers](https://github.com/adrianjagielak/homebridge-tuya-plus/blob/main/wiki/Supported-Device-Types.md#irrigation-systems--sprinklers)** for all options (per-zone durations, master switch, etc.).
132
-
133
- ---
134
-
135
- ## Realtime updates (MQTT)
69
+ ### Security note
136
70
 
137
- Live updates use Tuya's MQTT message service via the **`mqtt`** package, which is an *optional dependency* installed automatically. If it's missing (or `"realtime": false`), cloud devices still work and stay controllable, but external changes (a physical button, the device's own timer) won't show up until Homebridge restarts. To force-install it:
71
+ Your **Access Secret** and app password are sensitive. Keep them only in your Homebridge `config.json`. If you ever share a config for support, redact them.
138
72
 
139
- ```
140
- sudo npm install -g mqtt
141
- ```
73
+ ## Troubleshooting
142
74
 
143
- The realtime stream connects out to Tuya's broker on **port 8883** make sure your firewall allows that outbound (it's open on normal home networks).
75
+ ### A device reads fine over the cloud but won't accept commands (`command or value not support`, code `2008` / `2003`)
144
76
 
145
- ---
77
+ The plugin controls devices through Tuya's current **`iot-03`** device API (`POST /v1.0/iot-03/devices/{id}/commands`), the same one tinytuya and the official Tuya Homebridge plugin use. That works for almost everything.
146
78
 
147
- ## Troubleshooting
79
+ Some **custom data-points** (e.g. a gate/garage controller's open/close/stop) aren't in the device's standard instruction set — `/commands` answers `2003`/`2008` for them — yet the cloud still models them as **thing-model properties** (their status reads fine, and the device acts on the same write over the LAN). For these, **you don't need to configure anything**: when a `/commands` write is rejected with `2008`/`2003`, the plugin automatically retries it through Tuya's thing-model endpoint (`POST /v2.0/cloud/thing/{id}/shadow/properties/issue`) — which is how Tuya's own app drives such a device — and remembers the working endpoint **per data-point**. So a device that mixes ordinary DPs (a switch) with custom ones (a trigger) drives each over the endpoint it accepts; the standard commands API is always tried first.
148
80
 
149
- | Symptom | Cause / fix |
150
- |---|---|
151
- | `failed to connect to Tuya Cloud: token request failed … (code 1004)` | Wrong **Access ID/Secret**, or host clock skew — the signature includes a timestamp, so keep the machine **NTP-synced**. |
152
- | `… (code 1106) permission deny` / `No permissions` | API service not authorized or **trial expired** (step 2), or **wrong region**, or you logged in with the developer account instead of the **app** account. |
153
- | Device connects but shows nothing / `0` devices | **Region mismatch**, or the device isn't linked to the project (step 3), or the linked account isn't the device's **owner** (shared/guest access hides devices). |
154
- | `realtime disabled: the optional "mqtt" package is not installed` | Install `mqtt` (above), or ignore if you don't need live external updates. |
155
- | Realtime never connects (control works, external changes don't) | Outbound **port 8883** blocked by a firewall. |
156
- | Logs show different codes than expected | Use the codes from the startup log line shown above. |
157
-
158
- ### Security note
81
+ If a write fails on **both** endpoints, the data-point is genuinely **report-only** for that device (its Tuya product defines it that way), which only the manufacturer can change — there's no cloud workaround. Such devices still work normally over the **LAN**; only the cloud fallback can't drive them.
159
82
 
160
- Your **Access Secret** and app password are sensitive. Keep them only in your Homebridge `config.json`. If you ever share a config for support, redact them — and you can always reset the Access Secret on the Tuya IoT platform.
83
+ The undocumented `debug.logCloudHttp` switch logs the full (credential-redacted) request/response for every cloud call if you want to see exactly what was sent and how the cloud replied.