Skip to content

Commit 5bbe4e0

Browse files
Add device support for TS110E Model QS-Zigbee-D02-TRIAC-LN (#1422)
* Add device support for TS110E This device uses a non-default set level command. Tested in local docker environment. Thanks to @javicalle this was possible. Fix #1415 * Lint * Lint * Update ts110e.py * Update ts110e.py * Update ts110e.py * Update ts110e.py
1 parent d28409c commit 5bbe4e0

File tree

1 file changed

+178
-0
lines changed

1 file changed

+178
-0
lines changed

zhaquirks/tuya/ts110e.py

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
"""Tuya Dimmer TS110E."""
2+
from typing import Optional, Union
3+
4+
from zigpy.profiles import zha
5+
import zigpy.types as t
6+
from zigpy.zcl import foundation
7+
from zigpy.zcl.clusters.general import (
8+
Basic,
9+
GreenPowerProxy,
10+
Groups,
11+
LevelControl,
12+
OnOff,
13+
Ota,
14+
Scenes,
15+
Time,
16+
)
17+
18+
from zhaquirks.const import (
19+
DEVICE_TYPE,
20+
ENDPOINTS,
21+
INPUT_CLUSTERS,
22+
MODELS_INFO,
23+
OUTPUT_CLUSTERS,
24+
PROFILE_ID,
25+
)
26+
from zhaquirks.tuya import (
27+
NoManufacturerCluster,
28+
TuyaDimmerSwitch,
29+
TuyaZBExternalSwitchTypeCluster,
30+
)
31+
32+
TUYA_LEVEL_ATTRIBUTE = 0xF000
33+
TUYA_BULB_TYPE_ATTRIBUTE = 0xFC02
34+
TUYA_MIN_LEVEL_ATTRIBUTE = 0xFC03
35+
TUYA_MAX_LEVEL_ATTRIBUTE = 0xFC04
36+
TUYA_CUSTOM_LEVEL_COMMAND = 0x00F0
37+
38+
39+
class TuyaLevelPayload(t.Struct):
40+
"""Tuya Level payload."""
41+
42+
level: t.uint16_t
43+
transtime: t.uint16_t
44+
45+
46+
class TuyaBulbType(t.enum8):
47+
"""Tuya bulb type."""
48+
49+
LED = 0x00
50+
INCANDESCENT = 0x01
51+
HALOGEN = 0x02
52+
53+
54+
class F000LevelControlCluster(NoManufacturerCluster, LevelControl):
55+
"""LevelControlCluster that reports to attrid 0xF000."""
56+
57+
server_commands = LevelControl.server_commands.copy()
58+
server_commands[TUYA_CUSTOM_LEVEL_COMMAND] = foundation.ZCLCommandDef(
59+
"moveToLevelTuya",
60+
(TuyaLevelPayload,),
61+
is_manufacturer_specific=False,
62+
)
63+
64+
attributes = LevelControl.attributes.copy()
65+
attributes.update(
66+
{
67+
# 0xF000
68+
TUYA_LEVEL_ATTRIBUTE: ("manufacturer_current_level", t.uint16_t),
69+
# 0xFC02
70+
TUYA_BULB_TYPE_ATTRIBUTE: ("bulb_type", TuyaBulbType),
71+
# 0xFC03
72+
TUYA_MIN_LEVEL_ATTRIBUTE: ("manufacturer_min_level", t.uint16_t),
73+
# 0xFC04
74+
TUYA_MAX_LEVEL_ATTRIBUTE: ("manufacturer_max_level", t.uint16_t),
75+
}
76+
)
77+
78+
# 0xF000 reported values are 10-1000, convert to 0-254
79+
def _update_attribute(self, attrid, value):
80+
if attrid == TUYA_LEVEL_ATTRIBUTE:
81+
self.debug(
82+
"Getting brightness %s",
83+
value,
84+
)
85+
value = (value + 4 - 10) * 254 // (1000 - 10)
86+
attrid = 0x0000
87+
88+
super()._update_attribute(attrid, value)
89+
90+
async def command(
91+
self,
92+
command_id: Union[foundation.Command, int, t.uint8_t],
93+
*args,
94+
manufacturer: Optional[Union[int, t.uint16_t]] = None,
95+
expect_reply: bool = True,
96+
tsn: Optional[Union[int, t.uint8_t]] = None,
97+
):
98+
"""Override the default Cluster command."""
99+
self.debug(
100+
"Sending Cluster Command. Cluster Command is %x, Arguments are %s",
101+
command_id,
102+
args,
103+
)
104+
# move_to_level, move, move_to_level_with_on_off
105+
if command_id in (0x0000, 0x0001, 0x0004):
106+
# convert dim values to 10-1000
107+
brightness = args[0] * (1000 - 10) // 254 + 10
108+
self.debug(
109+
"Setting brightness to %s",
110+
brightness,
111+
)
112+
return await super().command(
113+
TUYA_CUSTOM_LEVEL_COMMAND,
114+
TuyaLevelPayload(level=brightness, transtime=0),
115+
manufacturer=manufacturer,
116+
expect_reply=expect_reply,
117+
tsn=tsn,
118+
)
119+
120+
return super().command(command_id, *args, manufacturer, expect_reply, tsn)
121+
122+
123+
class DimmerSwitchWithNeutral1Gang(TuyaDimmerSwitch):
124+
"""Tuya Dimmer Switch Module With Neutral 1 Gang."""
125+
126+
signature = {
127+
MODELS_INFO: [("_TZ3210_ngqk6jia", "TS110E")],
128+
ENDPOINTS: {
129+
# <SimpleDescriptor endpoint=1 profile=260 device_type=257
130+
# input_clusters=[0, 4, 5, 6, 8, 57345]
131+
# output_clusters=[10, 25]>
132+
1: {
133+
PROFILE_ID: zha.PROFILE_ID,
134+
DEVICE_TYPE: zha.DeviceType.DIMMABLE_LIGHT,
135+
INPUT_CLUSTERS: [
136+
Basic.cluster_id,
137+
Groups.cluster_id,
138+
Scenes.cluster_id,
139+
OnOff.cluster_id,
140+
LevelControl.cluster_id,
141+
TuyaZBExternalSwitchTypeCluster.cluster_id,
142+
],
143+
OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
144+
},
145+
242: {
146+
# <SimpleDescriptor endpoint=242 profile=41440 device_type=97
147+
# input_clusters=[]
148+
# output_clusters=[33]
149+
PROFILE_ID: 41440,
150+
DEVICE_TYPE: 97,
151+
INPUT_CLUSTERS: [],
152+
OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
153+
},
154+
},
155+
}
156+
replacement = {
157+
ENDPOINTS: {
158+
1: {
159+
PROFILE_ID: zha.PROFILE_ID,
160+
DEVICE_TYPE: zha.DeviceType.DIMMABLE_LIGHT,
161+
INPUT_CLUSTERS: [
162+
Basic.cluster_id,
163+
Groups.cluster_id,
164+
Scenes.cluster_id,
165+
OnOff.cluster_id,
166+
F000LevelControlCluster,
167+
TuyaZBExternalSwitchTypeCluster,
168+
],
169+
OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
170+
},
171+
242: {
172+
PROFILE_ID: 41440,
173+
DEVICE_TYPE: 97,
174+
INPUT_CLUSTERS: [],
175+
OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
176+
},
177+
},
178+
}

0 commit comments

Comments
 (0)