Skip to content

Commit fdcefe0

Browse files
authored
add ts0601 _TZE200_znzs7yaw (#1617)
1 parent 6f5ad0f commit fdcefe0

File tree

1 file changed

+287
-0
lines changed

1 file changed

+287
-0
lines changed

zhaquirks/tuya/ts0601_haozee.py

Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
"""Map from manufacturer to standard clusters for thermostatic valves."""
2+
import logging
3+
4+
import zigpy.profiles.zha
5+
import zigpy.types as t
6+
from zigpy.zcl.clusters.general import Basic, Groups, Ota, Scenes, Time
7+
8+
from zhaquirks.const import (
9+
DEVICE_TYPE,
10+
ENDPOINTS,
11+
INPUT_CLUSTERS,
12+
MODELS_INFO,
13+
OUTPUT_CLUSTERS,
14+
PROFILE_ID,
15+
)
16+
from zhaquirks.tuya import (
17+
TuyaManufClusterAttributes,
18+
TuyaThermostat,
19+
TuyaThermostatCluster,
20+
TuyaUserInterfaceCluster,
21+
)
22+
23+
# from https://github.com/Koenkk/zigbee-herdsman-converters/pull/1694/
24+
HAOZEE_SCHEDULE_WORKDAYS_AM_ATTR = (
25+
0x0077 # schedule for workdays [8, 0, 20, 8, 1, 15, 11, 30, 15]
26+
)
27+
HAOZEE_SCHEDULE_WORKDAYS_PM_ATTR = (
28+
0x0078 # schedule for workdays [13, 30, 15, 17, 0, 15, 150, 0, 15]
29+
)
30+
HAOZEE_SCHEDULE_WEEKEND_AM_ATTR = (
31+
0x0079 # schedule for weekend [6, 0, 20, 8, 0, 15, 11, 30, 15]
32+
)
33+
HAOZEE_SCHEDULE_WEEKEND_PM_ATTR = (
34+
0x007A # schedule for weekend [13, 30, 15, 17, 0, 15, 22, 0, 15]
35+
)
36+
37+
HAOZEE_HEATING_ENABLED_ATTR = 0x0166 # [0] idle [1] heating
38+
HAOZEE_MAX_TEMP_PROTECTION_ENABLED_ATTR = (
39+
0x016A # minimal temp protection [0] disabled [1] enabled
40+
)
41+
HAOZEE_MIN_TEMP_PROTECTION_ENABLED_ATTR = (
42+
0x016B # maximal temp protection [0] disabled [1] enabled
43+
)
44+
HAOZEE_ENABLED_ATTR = 0x017D # device status [0] disabled [1] enabled
45+
HAOZEE_CHILD_LOCK_ATTR = 0x0181 # [0] unlocked [1] child-locked
46+
HAOZEE_EXT_TEMP_ATTR = 0x0267 # external NTC sensor temp (decidegree)
47+
HAOZEE_AWAY_DAYS_ATTR = 0x0268 # away mode duration (days)
48+
HAOZEE_AWAY_TEMP_ATTR = 0x0269 # away mode temperature (decidegree)
49+
HAOZEE_TEMP_CALIBRATION_ATTR = 0x026D # temperature calibration (decidegree)
50+
HAOZEE_TEMP_HYSTERESIS_ATTR = 0x026E # temperature hysteresis (decidegree)
51+
HAOZEE_TEMP_PROTECT_HYSTERESIS_ATTR = (
52+
0x026F # temperature hysteresis for protection (decidegree)
53+
)
54+
HAOZEE_MAX_PROTECT_TEMP_ATTR = (
55+
0x0270 # maximal temp for protection trigger (decidegree)
56+
)
57+
HAOZEE_MIN_PROTECT_TEMP_ATTR = (
58+
0x0271 # minimal temp for protection trigger (decidegree)
59+
)
60+
HAOZEE_MAX_TEMP_LIMIT_ATTR = 0x0272 # maximum limit of temperature setting (decidegree)
61+
HAOZEE_MIN_TEMP_LIMIT_ATTR = 0x0273 # minimum limit of temperature setting (decidegree)
62+
HAOZEE_TARGET_TEMP_ATTR = 0x027E # target temperature (decidegree)
63+
HAOZEE_CURRENT_ROOM_TEMP_ATTR = (
64+
0x027F # temperature reported by MCU. Depends on sensor type. (decidegree)
65+
)
66+
HAOZEE_SENSOR_TYPE_ATTR = 0x0474 # sensor type [0] internal [1] external [2] both
67+
HAOZEE_POWERON_BEHAVIOR_ATTR = 0x0475 # poweron behavior [0] restore [1] off [2] on
68+
HAOZEE_WEEKFORMAT = 0x0476 # [0] 5+2 [1] 6+1 [2] 7 (days)
69+
70+
HAOZEE_CURRENT_MODE_ATTR = 0x0480 # [0] manual [1] auto [2] away
71+
HAOZEE_FAULT_ATTR = 0x0582 # Known fault codes: [4] E2 external sensor error
72+
73+
# Unknown DP - descaling on/off, window detection, window detection settings
74+
75+
_LOGGER = logging.getLogger(__name__)
76+
77+
78+
class HY08WEManufCluster(TuyaManufClusterAttributes):
79+
"""Manufacturer Specific Cluster of some thermostatic valves."""
80+
81+
# Important! This device uses offset from 2000 year for UTC time and offset from 1970 for local time
82+
set_time_offset = 2000
83+
set_time_local_offset = 1970
84+
85+
attributes = TuyaManufClusterAttributes.attributes.copy()
86+
attributes.update(
87+
{
88+
HAOZEE_HEATING_ENABLED_ATTR: ("heating_enabled", t.uint8_t),
89+
HAOZEE_MAX_TEMP_PROTECTION_ENABLED_ATTR: (
90+
"max_temp_protection_enabled",
91+
t.uint8_t,
92+
),
93+
HAOZEE_MIN_TEMP_PROTECTION_ENABLED_ATTR: (
94+
"min_temp_protection_enabled",
95+
t.uint8_t,
96+
),
97+
HAOZEE_ENABLED_ATTR: ("enabled", t.uint8_t),
98+
HAOZEE_CHILD_LOCK_ATTR: ("child_lock", t.uint8_t),
99+
HAOZEE_EXT_TEMP_ATTR: ("external_temperature", t.uint32_t),
100+
HAOZEE_AWAY_DAYS_ATTR: ("away_duration_days", t.uint32_t),
101+
HAOZEE_AWAY_TEMP_ATTR: ("away_mode_temperature", t.uint32_t),
102+
HAOZEE_TEMP_CALIBRATION_ATTR: ("temperature_calibration", t.int32s),
103+
HAOZEE_TEMP_HYSTERESIS_ATTR: ("hysterisis_temperature", t.uint32_t),
104+
HAOZEE_TEMP_PROTECT_HYSTERESIS_ATTR: (
105+
"hysterisis_protection_temperature",
106+
t.uint32_t,
107+
),
108+
HAOZEE_MAX_PROTECT_TEMP_ATTR: ("max_protection_temperature", t.uint32_t),
109+
HAOZEE_MIN_PROTECT_TEMP_ATTR: ("min_protection_temperature", t.uint32_t),
110+
HAOZEE_MAX_TEMP_LIMIT_ATTR: ("max_temperature", t.uint32_t),
111+
HAOZEE_MIN_TEMP_LIMIT_ATTR: ("min_temperature", t.uint32_t),
112+
HAOZEE_TARGET_TEMP_ATTR: ("target_temperature", t.uint32_t),
113+
HAOZEE_CURRENT_ROOM_TEMP_ATTR: ("internal_temperature", t.uint32_t),
114+
HAOZEE_SENSOR_TYPE_ATTR: ("sensor_settings", t.uint8_t),
115+
HAOZEE_POWERON_BEHAVIOR_ATTR: ("poweron_behavior", t.uint8_t),
116+
HAOZEE_WEEKFORMAT: ("week_format", t.uint8_t),
117+
HAOZEE_CURRENT_MODE_ATTR: ("mode", t.uint8_t),
118+
HAOZEE_FAULT_ATTR: ("fault", t.uint8_t),
119+
}
120+
)
121+
122+
DIRECT_MAPPED_ATTRS = {
123+
HAOZEE_CURRENT_ROOM_TEMP_ATTR: ("local_temp", lambda value: value * 10),
124+
HAOZEE_TARGET_TEMP_ATTR: (
125+
"occupied_heating_setpoint",
126+
lambda value: value * 10,
127+
),
128+
HAOZEE_AWAY_TEMP_ATTR: (
129+
"unoccupied_heating_setpoint",
130+
lambda value: value * 100,
131+
),
132+
HAOZEE_TEMP_CALIBRATION_ATTR: (
133+
"local_temperature_calibration",
134+
lambda value: value * 10,
135+
),
136+
HAOZEE_MIN_TEMP_LIMIT_ATTR: (
137+
"min_heat_setpoint_limit",
138+
lambda value: value * 100,
139+
),
140+
HAOZEE_MAX_TEMP_LIMIT_ATTR: (
141+
"max_heat_setpoint_limit",
142+
lambda value: value * 100,
143+
),
144+
HAOZEE_AWAY_DAYS_ATTR: ("unoccupied_duration_days", None),
145+
}
146+
147+
def _update_attribute(self, attrid, value):
148+
super()._update_attribute(attrid, value)
149+
if attrid in self.DIRECT_MAPPED_ATTRS:
150+
self.endpoint.device.thermostat_bus.listener_event(
151+
"temperature_change",
152+
self.DIRECT_MAPPED_ATTRS[attrid][0],
153+
value
154+
if self.DIRECT_MAPPED_ATTRS[attrid][1] is None
155+
else self.DIRECT_MAPPED_ATTRS[attrid][1](
156+
value
157+
), # decidegree to centidegree
158+
)
159+
elif attrid == HAOZEE_ENABLED_ATTR:
160+
self.endpoint.device.thermostat_bus.listener_event("enabled_change", value)
161+
elif attrid == HAOZEE_HEATING_ENABLED_ATTR:
162+
self.endpoint.device.thermostat_bus.listener_event("state_change", value)
163+
elif attrid == HAOZEE_CURRENT_MODE_ATTR:
164+
self.endpoint.device.thermostat_bus.listener_event("mode_change", value)
165+
elif attrid == HAOZEE_CHILD_LOCK_ATTR:
166+
self.endpoint.device.ui_bus.listener_event("child_lock_change", value)
167+
168+
169+
class HY08WEThermostat(TuyaThermostatCluster):
170+
"""Thermostat cluster for some thermostatic valves."""
171+
172+
DIRECT_MAPPING_ATTRS = {
173+
"occupied_heating_setpoint": (
174+
HAOZEE_TARGET_TEMP_ATTR,
175+
lambda value: round(value / 10),
176+
),
177+
"unoccupied_heating_setpoint": (
178+
HAOZEE_AWAY_TEMP_ATTR,
179+
lambda value: round(value / 100),
180+
),
181+
"min_heat_setpoint_limit": (
182+
HAOZEE_MIN_TEMP_LIMIT_ATTR,
183+
lambda value: round(value / 100),
184+
),
185+
"max_heat_setpoint_limit": (
186+
HAOZEE_MAX_TEMP_LIMIT_ATTR,
187+
lambda value: round(value / 100),
188+
),
189+
"local_temperature_calibration": (
190+
HAOZEE_TEMP_CALIBRATION_ATTR,
191+
lambda value: round(value / 10),
192+
),
193+
}
194+
195+
def map_attribute(self, attribute, value):
196+
"""Map standardized attribute value to dict of manufacturer values."""
197+
198+
if attribute in self.DIRECT_MAPPING_ATTRS:
199+
return {
200+
self.DIRECT_MAPPING_ATTRS[attribute][0]: value
201+
if self.DIRECT_MAPPING_ATTRS[attribute][1] is None
202+
else self.DIRECT_MAPPING_ATTRS[attribute][1](value)
203+
}
204+
205+
if attribute == "system_mode":
206+
if value == self.SystemMode.Off:
207+
return {HAOZEE_ENABLED_ATTR: 0}
208+
if value == self.SystemMode.Heat:
209+
return {HAOZEE_ENABLED_ATTR: 1}
210+
self.error("Unsupported value for SystemMode")
211+
212+
if attribute == "programing_oper_mode":
213+
if value == self.ProgrammingOperationMode.Simple:
214+
return {HAOZEE_CURRENT_MODE_ATTR: 0}
215+
if value == self.ProgrammingOperationMode.Schedule_programming_mode:
216+
return {HAOZEE_CURRENT_MODE_ATTR: 1}
217+
if value == self.ProgrammingOperationMode.Economy_mode:
218+
return {HAOZEE_CURRENT_MODE_ATTR: 2}
219+
self.error("Unsupported value for ProgrammingOperationMode")
220+
221+
def mode_change(self, value):
222+
"""System Mode change."""
223+
occupancy = self.Occupancy.Occupied
224+
if value == 0:
225+
prog_mode = self.ProgrammingOperationMode.Simple
226+
elif value == 1:
227+
prog_mode = self.ProgrammingOperationMode.Schedule_programming_mode
228+
elif value == 2:
229+
prog_mode = self.ProgrammingOperationMode.Simple
230+
occupancy = self.Occupancy.Unoccupied
231+
self._update_attribute(self.attridx["programing_oper_mode"], prog_mode)
232+
self._update_attribute(self.attridx["occupancy"], occupancy)
233+
234+
def enabled_change(self, value):
235+
"""System mode change."""
236+
if value == 0:
237+
mode = self.SystemMode.Off
238+
else:
239+
mode = self.SystemMode.Heat
240+
self._update_attribute(self.attridx["system_mode"], mode)
241+
242+
243+
class HY08WEUserInterface(TuyaUserInterfaceCluster):
244+
"""HVAC User interface cluster for tuya electric heating thermostats."""
245+
246+
_CHILD_LOCK_ATTR = HAOZEE_CHILD_LOCK_ATTR
247+
248+
249+
class HY08WE(TuyaThermostat):
250+
"""Haozee HY08WE Thermostatic radiator valve."""
251+
252+
signature = {
253+
# endpoint=1 profile=260 device_type=81 device_version=1 input_clusters=[0, 4, 5, 61184],
254+
# output_clusters=[25, 10]
255+
MODELS_INFO: [("_TZE200_znzs7yaw", "TS0601")],
256+
ENDPOINTS: {
257+
1: {
258+
PROFILE_ID: zigpy.profiles.zha.PROFILE_ID,
259+
DEVICE_TYPE: zigpy.profiles.zha.DeviceType.SMART_PLUG,
260+
INPUT_CLUSTERS: [
261+
Basic.cluster_id,
262+
Groups.cluster_id,
263+
Scenes.cluster_id,
264+
TuyaManufClusterAttributes.cluster_id,
265+
],
266+
OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
267+
}
268+
},
269+
}
270+
271+
replacement = {
272+
ENDPOINTS: {
273+
1: {
274+
PROFILE_ID: zigpy.profiles.zha.PROFILE_ID,
275+
DEVICE_TYPE: zigpy.profiles.zha.DeviceType.THERMOSTAT,
276+
INPUT_CLUSTERS: [
277+
Basic.cluster_id,
278+
Groups.cluster_id,
279+
Scenes.cluster_id,
280+
HY08WEManufCluster,
281+
HY08WEThermostat,
282+
HY08WEUserInterface,
283+
],
284+
OUTPUT_CLUSTERS: [Ota.cluster_id, Time.cluster_id],
285+
}
286+
}
287+
}

0 commit comments

Comments
 (0)