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
package/Readme.MD CHANGED
@@ -14,7 +14,7 @@
14
14
 
15
15
 
16
16
 
17
- A community-maintained Homebridge plugin for controlling Tuya devices locally over LAN. Control your supported Tuya accessories locally in HomeKit.
17
+ A community-maintained Homebridge plugin for controlling Tuya devices locally over LAN. Control your supported Tuya accessories locally in HomeKit, with an optional Tuya Cloud fallback for devices the LAN can't reach.
18
18
 
19
19
  * [Supported Device Types](#supported-device-types)
20
20
  * [Supported Tuya Protocol Versions](#supported-tuya-protocol-versions)
@@ -38,7 +38,7 @@ A community-maintained Homebridge plugin for controlling Tuya devices locally ov
38
38
  * Fan v2<sup>[7](https://github.com/adrianjagielak/homebridge-tuya-plus/blob/main/wiki/Supported-Device-Types.md)</sup>
39
39
  * Garages<sup>[8](https://github.com/adrianjagielak/homebridge-tuya-plus/blob/main/wiki/Supported-Device-Types.md#garage-doors)</sup>
40
40
  * Heaters<sup>[9](https://github.com/adrianjagielak/homebridge-tuya-plus/blob/main/wiki/Supported-Device-Types.md)</sup>
41
- * Irrigation Systems / Sprinklers<sup>[17](https://github.com/adrianjagielak/homebridge-tuya-plus/blob/main/wiki/Supported-Device-Types.md#irrigation-systems--sprinklers)</sup> (multi-valve, per-zone timers, rain sensor, battery)
41
+ * Irrigation Systems / Sprinklers<sup>[17](https://github.com/adrianjagielak/homebridge-tuya-plus/blob/main/wiki/Supported-Device-Types.md#irrigation-systems--sprinklers)</sup> (multi-valve, per-zone timers, battery)
42
42
  * Lights
43
43
  * On/Off<sup>[10](https://github.com/adrianjagielak/homebridge-tuya-plus/blob/main/wiki/Supported-Device-Types.md)</sup>
44
44
  * Brightness<sup>[11](https://github.com/adrianjagielak/homebridge-tuya-plus/blob/main/wiki/Supported-Device-Types.md#tunable-white-light-bulbs)</sup>
@@ -48,34 +48,11 @@ A community-maintained Homebridge plugin for controlling Tuya devices locally ov
48
48
  * Switches<sup>[15](https://github.com/adrianjagielak/homebridge-tuya-plus/blob/main/wiki/Supported-Device-Types.md#switch)</sup>
49
49
  * Vertical Blinds<sup>[16](https://github.com/adrianjagielak/homebridge-tuya-plus/blob/main/wiki/Supported-Device-Types.md#vertical-blinds-with-tilt)</sup>
50
50
 
51
- Note: Motion, and other sensor types don't behave well with responce requests, so they will not be added.
51
+ ## Cloud fallback
52
52
 
53
- ## Supported Tuya Protocol Versions
53
+ The plugin is LAN-first — everything is controlled locally by default. If you add Tuya Cloud credentials in a top-level `cloud` block, it keeps a cloud session in the background and uses it as a fallback: each device is tried on the LAN first, and only falls back to the cloud when it can't be reached locally — a brief LAN hiccup, or hardware that never appears on the LAN at all (e.g. battery-powered "sleepy" devices). It's opt-in and local always comes first, and existing configs need no changes — so it's also a handy fix if some devices occasionally show as "No Response".
54
54
 
55
- The plugin speaks every published Tuya LAN protocol version:
56
-
57
- | Tuya LAN protocol | Framing | Encryption | Status |
58
- |---|---|---|---|
59
- | 3.1 | `0x55AA` | AES-128-ECB (base64, control only) | ✅ Supported |
60
- | 3.2 | `0x55AA` | AES-128-ECB | ✅ Supported |
61
- | 3.3 | `0x55AA` | AES-128-ECB | ✅ Supported |
62
- | 3.4 | `0x55AA` | AES-128-ECB, session key, HMAC-SHA256 | ✅ Supported |
63
- | 3.5 | `0x6699` | AES-128-GCM, session key | ✅ Supported |
64
- | 3.6 and newer | — | — | ✅ Forward-compatible (see below) |
65
-
66
- The protocol version is auto-detected from the device's discovery broadcast, so you normally don't need to configure anything.
67
-
68
- Protocol **3.5** is currently the newest Tuya LAN protocol in existence: it is the latest version implemented by Tuya's own open-source device SDK ([TuyaOpen](https://github.com/tuya/TuyaOpen)) and by the reference reverse-engineered implementations ([tinytuya](https://github.com/jasonacox/tinytuya/blob/master/PROTOCOL.md)). No device firmware speaking a "3.6" protocol has been observed in the wild so far.
69
-
70
- This plugin is nevertheless **3.6-ready**: if a device reports a newer protocol version (e.g. `3.6`) in its broadcast, the plugin automatically talks to it using the newest (3.5/GCM) protocol stack while tagging payloads with the device's reported version — the same forward-compatibility strategy used by tinytuya. Should such a device misbehave, you can pin it to a specific protocol with the `forceVersion` device option (e.g. `"forceVersion": "3.5"`), or use `version` to set a protocol for devices that can't be discovered (e.g. on another subnet).
71
-
72
- ## Cloud devices (for hardware that can't be controlled locally)
73
-
74
- This is a **LAN-first** plugin — virtually every Tuya device is controlled locally, which is faster, more private, and keeps working without internet. A few devices, though, simply **can't** be reached over the LAN: battery-powered "sleepy" devices — for example the **irrigation / faucet timers** — sleep almost all the time and only ever connect out to Tuya's cloud, so they never answer on the local network.
75
-
76
- For exactly these cases the plugin can talk to a device through the **Tuya Cloud** instead — strictly **opt-in, per device**. You add your Tuya Cloud project credentials once and set `"cloud": true` on the device; everything else works the same.
77
-
78
- 👉 **[Tuya Cloud Setup guide](https://github.com/adrianjagielak/homebridge-tuya-plus/blob/main/wiki/Tuya-Cloud-Setup.md)** — how to get credentials and configure a cloud device.
55
+ See the **[Tuya Cloud Setup guide](https://github.com/adrianjagielak/homebridge-tuya-plus/blob/main/wiki/Tuya-Cloud-Setup.md)** for credentials and setup.
79
56
 
80
57
  ## Installation Instructions
81
58
 
@@ -92,14 +69,13 @@ sudo npm install -g homebridge-tuya-plus
92
69
  #### Bleeding-edge (`dev`) builds:
93
70
 
94
71
  Every commit to `main` is automatically published to npm under the `dev` tag — a
95
- separate, unstable channel. The stable release always stays on `latest`, so this
96
- won't affect normal installs. To try the latest in-development build:
72
+ separate, unstable channel. To try the latest in-development build:
97
73
 
98
74
  ```
99
75
  sudo npm install -g homebridge-tuya-plus@dev
100
76
  ```
101
77
 
102
- These are versioned like `3.13.1-dev.<n>` and may be unstable; use the default
78
+ These are versioned like `3.14.0-dev.<n>` and may be unstable; use the default
103
79
  install above for production.
104
80
 
105
81
  ## Configuration
@@ -134,7 +110,7 @@ If you have new accessory logic for a new device, please add a function defined
134
110
 
135
111
  #
136
112
 
137
- Go check out my other Homebridge plugins:
113
+ Check out my other Homebridge plugins:
138
114
 
139
115
  * [homebridge-futurehome](https://github.com/adrianjagielak/homebridge-futurehome) ([npm](https://npmjs.com/package/homebridge-futurehome))
140
116
  * [homebridge-tuya-plus](https://github.com/adrianjagielak/homebridge-tuya-plus) ([npm](https://npmjs.com/package/homebridge-tuya-plus))
@@ -21,8 +21,8 @@
21
21
  },
22
22
  "cloud": {
23
23
  "type": "object",
24
- "title": "Tuya Cloud (optional — only for devices that can't be reached over the LAN)",
25
- "description": "This plugin is LAN-first. A few devices — notably battery-powered 'sleepy' irrigation timers — sleep most of the time and only ever talk to Tuya's cloud, so they cannot be controlled locally. Create a free Cloud project at iot.tuya.com, enter its credentials here, then tick 'Control via Tuya Cloud' on each such device. See the wiki for step-by-step setup.",
24
+ "title": "Tuya Cloud fallback (optional)",
25
+ "description": "<a href='https://github.com/adrianjagielak/homebridge-tuya-plus/blob/main/wiki/Tuya-Cloud-Setup.md'>Tuya Cloud API Setup</a>.",
26
26
  "properties": {
27
27
  "accessId": {
28
28
  "type": "string",
@@ -69,12 +69,6 @@
69
69
  { "title": "Tuya Smart", "enum": ["tuyaSmart"] },
70
70
  { "title": "Smart Life", "enum": ["smartlife"] }
71
71
  ]
72
- },
73
- "realtime": {
74
- "type": "boolean",
75
- "title": "Realtime updates (MQTT)",
76
- "default": true,
77
- "description": "Receive instant updates over Tuya's MQTT message service (uses the 'mqtt' package, installed automatically). If disabled, devices stay controllable and show their state at startup, but external changes (physical buttons, the device's own timers) won't be reflected until restart."
78
72
  }
79
73
  }
80
74
  },
@@ -96,7 +90,7 @@
96
90
  {
97
91
  "title": "Switch Gang / Basic Switch",
98
92
  "enum": ["Switch"]
99
- },
93
+ },
100
94
  {
101
95
  "title": "Smart Plug / Barely Smart Power Strip",
102
96
  "enum": ["Outlet"]
@@ -197,16 +191,7 @@
197
191
  "key": {
198
192
  "title": "Tuya Key (local key — required for LAN devices)",
199
193
  "type": "string",
200
- "description": "The device's local key. Required for normal (LAN) devices. Not needed for cloud devices (tick 'Control via Tuya Cloud' below).",
201
- "condition": {
202
- "functionBody": "return model.devices && model.devices[arrayIndices].type !== 'null';"
203
- }
204
- },
205
- "cloud": {
206
- "type": ["boolean", "object"],
207
- "title": "Control via Tuya Cloud (instead of the LAN)",
208
- "default": false,
209
- "description": "Enable for devices that can't be reached locally (e.g. battery-powered irrigation timers). Uses the platform-level 'cloud' credentials above. Advanced: instead of a checkbox you can set this to an object with per-device { accessId, accessKey, region, … } to use different credentials for this one device.",
194
+ "description": "The device's local key. Required for normal (LAN) devices. A device with no key is treated as cloud-only and is reached through the Tuya Cloud session configured above (e.g. a battery-powered 'sleepy' irrigation timer).",
210
195
  "condition": {
211
196
  "functionBody": "return model.devices && model.devices[arrayIndices].type !== 'null';"
212
197
  }
@@ -318,9 +303,9 @@
318
303
  "title": "Sync brightness to WLED (IP[:port])",
319
304
  "placeholder": "192.168.1.100",
320
305
  "condition": {
321
- "functionBody": "return model.devices && model.devices[arrayIndices] && ['WledDimmer', 'SimpleDimmer'].includes(model.devices[arrayIndices].type);"
306
+ "functionBody": "return model.devices && model.devices[arrayIndices] && ['WledDimmer'].includes(model.devices[arrayIndices].type);"
322
307
  }
323
- },
308
+ },
324
309
  "dpColorTemperature": {
325
310
  "type": "integer",
326
311
  "placeholder": "3",
@@ -421,6 +406,12 @@
421
406
  "functionBody": "return model.devices && model.devices[arrayIndices] && ['AirConditioner'].includes(model.devices[arrayIndices].type);"
422
407
  }
423
408
  },
409
+ "noAuto": {
410
+ "type": "boolean",
411
+ "condition": {
412
+ "functionBody": "return model.devices && model.devices[arrayIndices] && ['AirConditioner'].includes(model.devices[arrayIndices].type);"
413
+ }
414
+ },
424
415
  "cmdCool": {
425
416
  "type": "string",
426
417
  "placeholder": "COOL",
@@ -435,6 +426,32 @@
435
426
  "functionBody": "return model.devices && model.devices[arrayIndices] && ['AirConditioner'].includes(model.devices[arrayIndices].type);"
436
427
  }
437
428
  },
429
+ "cmdAuto": {
430
+ "type": "string",
431
+ "placeholder": "AUTO",
432
+ "condition": {
433
+ "functionBody": "return model.devices && model.devices[arrayIndices] && ['AirConditioner'].includes(model.devices[arrayIndices].type);"
434
+ }
435
+ },
436
+ "noRotationSpeed": {
437
+ "type": "boolean",
438
+ "condition": {
439
+ "functionBody": "return model.devices && model.devices[arrayIndices] && ['AirConditioner'].includes(model.devices[arrayIndices].type);"
440
+ }
441
+ },
442
+ "fanSpeedSteps": {
443
+ "type": "integer",
444
+ "placeholder": "3",
445
+ "condition": {
446
+ "functionBody": "return model.devices && model.devices[arrayIndices] && ['AirConditioner'].includes(model.devices[arrayIndices].type);"
447
+ }
448
+ },
449
+ "noChildLock": {
450
+ "type": "boolean",
451
+ "condition": {
452
+ "functionBody": "return model.devices && model.devices[arrayIndices] && ['AirConditioner', 'Convector'].includes(model.devices[arrayIndices].type);"
453
+ }
454
+ },
438
455
  "noSwing": {
439
456
  "type": "boolean",
440
457
  "condition": {
@@ -569,30 +586,31 @@
569
586
  "functionBody": "return model.devices && model.devices[arrayIndices] && ['Convector'].includes(model.devices[arrayIndices].type);"
570
587
  }
571
588
  },
572
- "noChildLock": {
589
+ "noTemperatureUnit": {
573
590
  "type": "boolean",
574
591
  "condition": {
575
592
  "functionBody": "return model.devices && model.devices[arrayIndices] && ['Convector'].includes(model.devices[arrayIndices].type);"
576
593
  }
577
594
  },
578
- "noTemperatureUnit": {
579
- "type": "boolean",
595
+ "temperatureDivisor": {
596
+ "type": "integer",
597
+ "placeholder": "1",
580
598
  "condition": {
581
- "functionBody": "return model.devices && model.devices[arrayIndices] && ['Convector'].includes(model.devices[arrayIndices].type);"
599
+ "functionBody": "return model.devices && model.devices[arrayIndices] && ['SimpleHeater', 'AirConditioner'].includes(model.devices[arrayIndices].type);"
582
600
  }
583
601
  },
584
- "temperatureDivisor": {
602
+ "currentTemperatureDivisor": {
585
603
  "type": "integer",
586
604
  "placeholder": "1",
587
605
  "condition": {
588
- "functionBody": "return model.devices && model.devices[arrayIndices] && ['SimpleHeater'].includes(model.devices[arrayIndices].type);"
606
+ "functionBody": "return model.devices && model.devices[arrayIndices] && ['AirConditioner'].includes(model.devices[arrayIndices].type);"
589
607
  }
590
608
  },
591
609
  "thresholdTemperatureDivisor": {
592
610
  "type": "integer",
593
611
  "placeholder": "1",
594
612
  "condition": {
595
- "functionBody": "return model.devices && model.devices[arrayIndices] && ['SimpleHeater'].includes(model.devices[arrayIndices].type);"
613
+ "functionBody": "return model.devices && model.devices[arrayIndices] && ['SimpleHeater', 'AirConditioner'].includes(model.devices[arrayIndices].type);"
596
614
  }
597
615
  },
598
616
  "temperatureOffset": {
@@ -717,7 +735,7 @@
717
735
  "type": "integer",
718
736
  "title": "Number of Valves / Zones",
719
737
  "placeholder": 4,
720
- "description": "How many valves/zones the controller has. They are assumed to be on data-points 1, 2, 3, … (or, for cloud devices, on the codes switch_1, switch_2, …). For non-sequential data-points use the 'valves' list instead.",
738
+ "description": "How many valves/zones the controller has. They are assumed to be on data-points 1, 2, 3, … (over the cloud these map to the codes switch_1, switch_2, … automatically). For non-sequential data-points use the 'valves' list instead.",
721
739
  "condition": {
722
740
  "functionBody": "return model.devices && model.devices[arrayIndices] && ['IrrigationSystem'].includes(model.devices[arrayIndices].type);"
723
741
  }
@@ -730,9 +748,27 @@
730
748
  "items": {
731
749
  "type": "object",
732
750
  "properties": {
733
- "name": { "type": "string", "title": "Zone name", "placeholder": "Lawn" },
734
- "dp": { "type": ["integer", "string"], "title": "Data-point (numeric id, or cloud code)", "placeholder": "1 or switch_1" },
735
- "defaultDuration": { "type": "integer", "title": "Default run time (seconds, 0 = run indefinitely)", "placeholder": 600 }
751
+ "name": {
752
+ "type": "string",
753
+ "title": "Zone name",
754
+ "placeholder": "Lawn"
755
+ },
756
+ "dp": {
757
+ "type": ["integer", "string"],
758
+ "title": "Data-point (numeric id, or cloud code)",
759
+ "placeholder": "1 or switch_1"
760
+ },
761
+ "dpCountdown": {
762
+ "type": ["integer", "string"],
763
+ "title": "Countdown data-point (numeric id, or cloud code)",
764
+ "description": "Optional. The device's built-in auto-off timer for this zone. Defaults to the switch data-point + 16 (so switch 1 → countdown 17). Set this only for code-addressed or non-standard zones.",
765
+ "placeholder": "17 or countdown_1"
766
+ },
767
+ "defaultDuration": {
768
+ "type": "integer",
769
+ "title": "Default run time (seconds, 0 = run indefinitely)",
770
+ "placeholder": 600
771
+ }
736
772
  }
737
773
  },
738
774
  "condition": {
@@ -752,7 +788,16 @@
752
788
  "type": "integer",
753
789
  "title": "Maximum Run Time (seconds)",
754
790
  "placeholder": 7200,
755
- "description": "Upper bound advertised to HomeKit for the duration picker (HAP default is 3600 / 1h). Apple's Home app only offers presets up to 1h regardless; longer values can be set from the config or apps like Eve.",
791
+ "description": "Upper bound advertised to HomeKit for the duration picker (HAP default is 3600 / 1h). Apple's Home app only offers presets up to 1h regardless; longer values can be set from the config or apps like Eve. Also bounds the hardware countdown: a device countdown longer than this (or the 120-min hardware cap) is corrected on connect.",
792
+ "condition": {
793
+ "functionBody": "return model.devices && model.devices[arrayIndices] && ['IrrigationSystem'].includes(model.devices[arrayIndices].type);"
794
+ }
795
+ },
796
+ "nativeCountdown": {
797
+ "type": "boolean",
798
+ "title": "Use the device's built-in countdown timer",
799
+ "placeholder": true,
800
+ "description": "When the controller exposes per-zone countdown data-points (countdown_1.., DP 17.. by default), mirror each zone's duration to the hardware timer so a zone still auto-closes on schedule even if Homebridge or the network drops out mid-run. Disable to fall back to the software-timer-only behaviour. Has no effect on devices that don't report a countdown data-point.",
756
801
  "condition": {
757
802
  "functionBody": "return model.devices && model.devices[arrayIndices] && ['IrrigationSystem'].includes(model.devices[arrayIndices].type);"
758
803
  }
@@ -817,55 +862,10 @@
817
862
  "condition": {
818
863
  "functionBody": "return model.devices && model.devices[arrayIndices] && ['IrrigationSystem'].includes(model.devices[arrayIndices].type) && !model.devices[arrayIndices].noBattery;"
819
864
  }
820
- },
821
- "noRainSensor": {
822
- "type": "boolean",
823
- "title": "No rain sensor",
824
- "description": "Disable the rain sensor (use if your controller doesn't report 'rain_sensor_state').",
825
- "condition": {
826
- "functionBody": "return model.devices && model.devices[arrayIndices] && ['IrrigationSystem'].includes(model.devices[arrayIndices].type);"
827
- }
828
- },
829
- "rainSensorType": {
830
- "type": "string",
831
- "title": "Rain sensor type",
832
- "default": "contact",
833
- "oneOf": [
834
- { "title": "Contact sensor (Open / Closed)", "enum": ["contact"] },
835
- { "title": "Leak sensor (Leak Detected / Dry — note: fires critical alerts)", "enum": ["leak"] }
836
- ],
837
- "condition": {
838
- "functionBody": "return model.devices && model.devices[arrayIndices] && ['IrrigationSystem'].includes(model.devices[arrayIndices].type) && !model.devices[arrayIndices].noRainSensor;"
839
- }
840
- },
841
- "rainInverted": {
842
- "type": "boolean",
843
- "title": "Invert rain sensor",
844
- "description": "Flip the reported state if 'raining' and 'dry' appear reversed in HomeKit.",
845
- "condition": {
846
- "functionBody": "return model.devices && model.devices[arrayIndices] && ['IrrigationSystem'].includes(model.devices[arrayIndices].type) && !model.devices[arrayIndices].noRainSensor;"
847
- }
848
- },
849
- "dpRain": {
850
- "type": ["integer", "string"],
851
- "placeholder": "49 or rain_sensor_state",
852
- "title": "Rain sensor data-point (numeric id, or cloud code)",
853
- "condition": {
854
- "functionBody": "return model.devices && model.devices[arrayIndices] && ['IrrigationSystem'].includes(model.devices[arrayIndices].type) && !model.devices[arrayIndices].noRainSensor;"
855
- }
856
- },
857
- "rainOnValue": {
858
- "type": "string",
859
- "placeholder": "rain",
860
- "title": "Rain sensor 'raining' value",
861
- "description": "The enum value the device reports when it is raining (default 'rain').",
862
- "condition": {
863
- "functionBody": "return model.devices && model.devices[arrayIndices] && ['IrrigationSystem'].includes(model.devices[arrayIndices].type) && !model.devices[arrayIndices].noRainSensor;"
864
- }
865
865
  }
866
866
  }
867
867
  }
868
868
  }
869
869
  }
870
870
  }
871
- }
871
+ }