Skip to content

Commit f111b98

Browse files
authored
Merge pull request #703 from mintsoft/master
Adding heated towel radiator element, AFD02
2 parents 1b3f3bc + 8aab0d3 commit f111b98

2 files changed

Lines changed: 165 additions & 1 deletion

File tree

tinytuya/Contrib/README.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ In addition to the built-in `OutletDevice`, `BulbDevice` and `CoverDevice` devic
228228
* WiFiDualMeterDevice - A community-contributed Python module to add support for Tuya WiFi Dual Meter device
229229
* Author: [Guillaume Gardet](https://github.com/ggardet)
230230

231-
```
231+
```python
232232
from tinytuya.Contrib import WiFiDualMeterDevice
233233

234234
wdm = WiFiDualMeterDevice.WiFiDualMeterDevice(
@@ -243,8 +243,35 @@ wdm.print_all()
243243
# Only print Voltage and frequency
244244
print(wdm.get_freq())
245245
print(wdm.get_voltage())
246+
```
247+
248+
### TowelRailHeaterDevice
249+
250+
* TowelRailHeaterDevice - A community-contributed Python module to add support for Tuya WiFi Towel Rail Heater
251+
* Author: [Rob Emery](https://github.com/mintsoft)
252+
253+
The mode is mapped as follows:
254+
* "auto" is the scheduler, I could not find a way to set or configure the schedule through Tuya, only physically on the device
255+
* "hot" is the user configured temperature (`set_target_temperature`)
256+
* "cold" is the hard-coded 50 degrees C
257+
* "eco" is the heat for a defined duration and then switches back off.
258+
259+
```python
260+
from tinytuya.Contrib import TowelRailHeaterDevice
261+
262+
device = TowelRailHeaterDevice.TowelRailHeaterDevice(
263+
dev_id='YOUR_DEV_ID',
264+
address='192.168.XX.YY', # Or set to 'Auto' to auto-discover IP address
265+
local_key='LOCAL_KEY',
266+
version=3.4)
246267

268+
# Print all known values
269+
print(device.status_json())
270+
271+
print(device.get_operating_mode())
272+
print(device.get_current_temperature())
247273

274+
```
248275
## Submit Your Device
249276

250277
* We welcome new device modules!
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
from tinytuya.core import Device
2+
3+
"""
4+
Python module to interface with Tuya Heated Towel Rail devices
5+
6+
Local Control Classes
7+
TowelRailHeaterDevice(..., version=3.4)
8+
This class uses a default version of 3.4
9+
See OutletDevice() for the other constructor arguments
10+
11+
Functions
12+
TowelRailHeaterDevice:
13+
status_json()
14+
get_current_temperature()
15+
get_target_temperature()
16+
set_target_temperature()
17+
get_operating_mode()
18+
set_operating_mode()
19+
get_current_state()
20+
get_timer() # returns number of minutes
21+
set_timer() # expects number of minutes
22+
Inherited
23+
json = status() # returns json payload
24+
set_version(version) # protocol version (defaults to 3.4 for this device)
25+
set_socketPersistent(False/True) # False [default] or True
26+
set_socketNODELAY(False/True) # False or True [default]
27+
set_socketRetryLimit(integer) # retry count limit [default 5]
28+
set_socketTimeout(timeout) # set connection timeout in seconds [default 5]
29+
set_dpsUsed(dps_to_request) # add data points (DPS) to request
30+
add_dps_to_request(index) # add data point (DPS) index set to None
31+
set_retry(retry=True) # retry if response payload is truncated
32+
set_status(on, switch=1, nowait) # Set status of device to 'on' or 'off' (bool)
33+
set_value(index, value, nowait) # Set int value of any index.
34+
heartbeat(nowait) # Send heartbeat to device
35+
updatedps(index=[1], nowait) # Send updatedps command to device
36+
turn_on(switch=1, nowait) # Turn on device
37+
turn_off(switch=1, nowait) # Turn off
38+
set_timer(num_secs, nowait) # Set timer for num_secs
39+
set_debug(toggle, color) # Activate verbose debugging output
40+
set_sendWait(num_secs) # Time to wait after sending commands before pulling response
41+
detect_available_dps() # Return list of DPS available from device
42+
generate_payload(command, data) # Generate TuyaMessage payload for command with data
43+
send(payload) # Send payload to device (do not wait for response)
44+
receive()
45+
"""
46+
47+
48+
class TowelRailHeaterDevice(Device):
49+
"""
50+
Represents a Tuya based Towel Rail Heating Element
51+
"""
52+
53+
DPS_POWER = "1"
54+
DPS_SET_TEMP = "16"
55+
DPS_CUR_TEMP = "24"
56+
DPS_MODE = "2"
57+
DPS_TIMER = "111"
58+
59+
def __init__(self, *args, **kwargs):
60+
# set the default version to 3.4 as that is what my device uses
61+
if "version" not in kwargs or not kwargs["version"]:
62+
kwargs["version"] = 3.4
63+
super(TowelRailHeaterDevice, self).__init__(*args, **kwargs)
64+
65+
def status_json(self):
66+
"""Wrapper around status() that replaces DPS indices with human readable labels."""
67+
status = self.status()["dps"]
68+
return {
69+
"Power On": status[self.DPS_POWER],
70+
"Set temperature": self.tuya_temperature_to_celsius(status[self.DPS_SET_TEMP]),
71+
"Current temperature": self.tuya_temperature_to_celsius(status[self.DPS_CUR_TEMP]),
72+
"Operating mode": status[self.DPS_MODE],
73+
"Timer": self.tuya_duration_to_minutes(status[self.DPS_TIMER]),
74+
}
75+
76+
def tuya_temperature_to_celsius(self, temperature):
77+
return round(float(temperature) / 10, 1)
78+
79+
def celsius_to_tuya_temperature(self, temperature):
80+
return int(round(float(temperature) * 10))
81+
82+
def get_current_temperature(self):
83+
status = self.status()["dps"]
84+
return self.tuya_temperature_to_celsius(status[self.DPS_CUR_TEMP])
85+
86+
def get_target_temperature(self):
87+
status = self.status()["dps"]
88+
return self.tuya_temperature_to_celsius(status[self.DPS_SET_TEMP])
89+
90+
def set_target_temperature(self, t):
91+
def is_float(f):
92+
try:
93+
float(f)
94+
return True
95+
except ValueError:
96+
return False
97+
98+
# non numeric values can confuse the unit
99+
if not is_float(t):
100+
return
101+
102+
target = float(t)
103+
self.set_value(self.DPS_SET_TEMP, self.celsius_to_tuya_temperature(target))
104+
105+
def get_operating_mode(self):
106+
status = self.status()["dps"]
107+
return status[self.DPS_MODE]
108+
109+
def set_operating_mode(self, mode):
110+
if mode not in ("cold", "hot", "eco", "auto"):
111+
return
112+
self.set_value(self.DPS_MODE, mode)
113+
114+
def get_current_state(self):
115+
status = self.status()["dps"]
116+
return "On" if status[self.DPS_POWER] else "Off"
117+
118+
def tuya_duration_to_minutes(self, duration):
119+
# The devices returns 10 for 1 hour, 15 for 1 hour 30, 20 for 2 hours
120+
# but it seems more intuitive to use minutes as our interface
121+
return duration * 6
122+
123+
def minutes_to_tuya_duration(self, duration):
124+
# The device expects 10 for 1 hour, 15 for 1 hour 30, 20 for 2 hours
125+
# so we need to convert from minutes into this format
126+
return int(duration / 6)
127+
128+
def get_timer(self):
129+
status = self.status()["dps"]
130+
return self.tuya_duration_to_minutes(status[self.DPS_TIMER])
131+
132+
def set_timer(self, delay):
133+
if delay < 30 or delay > 8 * 60: # 8 hours maximum
134+
return
135+
if delay % 30 != 0:
136+
return
137+
self.set_value(self.DPS_TIMER, self.minutes_to_tuya_duration(delay))

0 commit comments

Comments
 (0)