-
Notifications
You must be signed in to change notification settings - Fork 675
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add device support for TS110E Model QS-Zigbee-D02-TRIAC-LN #1422
Changes from all commits
cff1380
a3a37bf
afd2b8a
92a2f41
cdd1184
25ab689
b4c49fd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
"""Tuya Dimmer TS110E.""" | ||
from typing import Optional, Union | ||
|
||
from zigpy.profiles import zha | ||
import zigpy.types as t | ||
from zigpy.zcl import foundation | ||
from zigpy.zcl.clusters.general import ( | ||
Basic, | ||
GreenPowerProxy, | ||
Groups, | ||
LevelControl, | ||
OnOff, | ||
Ota, | ||
Scenes, | ||
Time, | ||
) | ||
|
||
from zhaquirks.const import ( | ||
DEVICE_TYPE, | ||
ENDPOINTS, | ||
INPUT_CLUSTERS, | ||
MODELS_INFO, | ||
OUTPUT_CLUSTERS, | ||
PROFILE_ID, | ||
) | ||
from zhaquirks.tuya import ( | ||
NoManufacturerCluster, | ||
TuyaDimmerSwitch, | ||
TuyaZBExternalSwitchTypeCluster, | ||
) | ||
|
||
TUYA_LEVEL_ATTRIBUTE = 0xF000 | ||
TUYA_BULB_TYPE_ATTRIBUTE = 0xFC02 | ||
TUYA_MIN_LEVEL_ATTRIBUTE = 0xFC03 | ||
TUYA_MAX_LEVEL_ATTRIBUTE = 0xFC04 | ||
TUYA_CUSTOM_LEVEL_COMMAND = 0x00F0 | ||
|
||
|
||
class TuyaLevelPayload(t.Struct): | ||
"""Tuya Level payload.""" | ||
|
||
level: t.uint16_t | ||
transtime: t.uint16_t | ||
|
||
|
||
class TuyaBulbType(t.enum8): | ||
"""Tuya bulb type.""" | ||
|
||
LED = 0x00 | ||
INCANDESCENT = 0x01 | ||
HALOGEN = 0x02 | ||
|
||
|
||
class F000LevelControlCluster(NoManufacturerCluster, LevelControl): | ||
"""LevelControlCluster that reports to attrid 0xF000.""" | ||
|
||
server_commands = LevelControl.server_commands.copy() | ||
server_commands[TUYA_CUSTOM_LEVEL_COMMAND] = foundation.ZCLCommandDef( | ||
"moveToLevelTuya", | ||
(TuyaLevelPayload,), | ||
is_manufacturer_specific=False, | ||
) | ||
|
||
attributes = LevelControl.attributes.copy() | ||
attributes.update( | ||
{ | ||
# 0xF000 | ||
TUYA_LEVEL_ATTRIBUTE: ("manufacturer_current_level", t.uint16_t), | ||
# 0xFC02 | ||
TUYA_BULB_TYPE_ATTRIBUTE: ("bulb_type", TuyaBulbType), | ||
# 0xFC03 | ||
TUYA_MIN_LEVEL_ATTRIBUTE: ("manufacturer_min_level", t.uint16_t), | ||
# 0xFC04 | ||
TUYA_MAX_LEVEL_ATTRIBUTE: ("manufacturer_max_level", t.uint16_t), | ||
} | ||
) | ||
|
||
# 0xF000 reported values are 10-1000, convert to 0-254 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The conversion is 1-255 (which I think is the range that HA handles). And the values 10 & 1000 I think correspond to the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've experimented with this quite a while and this is what worked best for me. Would you agree to merging this as is (because it seems to work with the default There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree. We can go ahead with the version as is. |
||
def _update_attribute(self, attrid, value): | ||
if attrid == TUYA_LEVEL_ATTRIBUTE: | ||
self.debug( | ||
"Getting brightness %s", | ||
value, | ||
) | ||
value = (value + 4 - 10) * 254 // (1000 - 10) | ||
attrid = 0x0000 | ||
|
||
super()._update_attribute(attrid, value) | ||
|
||
async def command( | ||
self, | ||
command_id: Union[foundation.Command, int, t.uint8_t], | ||
*args, | ||
manufacturer: Optional[Union[int, t.uint16_t]] = None, | ||
expect_reply: bool = True, | ||
tsn: Optional[Union[int, t.uint8_t]] = None, | ||
): | ||
"""Override the default Cluster command.""" | ||
self.debug( | ||
"Sending Cluster Command. Cluster Command is %x, Arguments are %s", | ||
command_id, | ||
args, | ||
) | ||
# move_to_level, move, move_to_level_with_on_off | ||
if command_id in (0x0000, 0x0001, 0x0004): | ||
# convert dim values to 10-1000 | ||
brightness = args[0] * (1000 - 10) // 254 + 10 | ||
self.debug( | ||
"Setting brightness to %s", | ||
brightness, | ||
) | ||
return await super().command( | ||
TUYA_CUSTOM_LEVEL_COMMAND, | ||
TuyaLevelPayload(level=brightness, transtime=0), | ||
manufacturer=manufacturer, | ||
expect_reply=expect_reply, | ||
tsn=tsn, | ||
) | ||
Comment on lines
+112
to
+118
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is fine to me, but probably you can get the same result without extending the
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm that doesn't work because then this Class would also show up as cluster in the dimmers of other devices. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That sentence not make sense to me. A quirk shouldn't modify other devices that don't use the quirk. # attributes = LevelControl.attributes.copy()
attributes.update(
{
# 0xF000
TUYA_LEVEL_ATTRIBUTE: ("manufacturer_current_level", t.uint16_t),
}
) If not making a copy of parent class attributes, maybe the code is modifying the parent attributes and changes for any device that uses the parent class 🤔 In any case, your proposed code is fine to me. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm yeah I don’t know, but it did show up as level control cluster on this new dimmer I bought… |
||
|
||
return super().command(command_id, *args, manufacturer, expect_reply, tsn) | ||
|
||
|
||
class DimmerSwitchWithNeutral1Gang(TuyaDimmerSwitch): | ||
"""Tuya Dimmer Switch Module With Neutral 1 Gang.""" | ||
|
||
signature = { | ||
MODELS_INFO: [("_TZ3210_ngqk6jia", "TS110E")], | ||
ENDPOINTS: { | ||
# <SimpleDescriptor endpoint=1 profile=260 device_type=257 | ||
# input_clusters=[0, 4, 5, 6, 8, 57345] | ||
# output_clusters=[10, 25]> | ||
1: { | ||
PROFILE_ID: zha.PROFILE_ID, | ||
DEVICE_TYPE: zha.DeviceType.DIMMABLE_LIGHT, | ||
INPUT_CLUSTERS: [ | ||
Basic.cluster_id, | ||
Groups.cluster_id, | ||
Scenes.cluster_id, | ||
OnOff.cluster_id, | ||
LevelControl.cluster_id, | ||
TuyaZBExternalSwitchTypeCluster.cluster_id, | ||
], | ||
OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id], | ||
}, | ||
242: { | ||
# <SimpleDescriptor endpoint=242 profile=41440 device_type=97 | ||
# input_clusters=[] | ||
# output_clusters=[33] | ||
PROFILE_ID: 41440, | ||
DEVICE_TYPE: 97, | ||
INPUT_CLUSTERS: [], | ||
OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id], | ||
}, | ||
}, | ||
} | ||
replacement = { | ||
ENDPOINTS: { | ||
1: { | ||
PROFILE_ID: zha.PROFILE_ID, | ||
DEVICE_TYPE: zha.DeviceType.DIMMABLE_LIGHT, | ||
INPUT_CLUSTERS: [ | ||
Basic.cluster_id, | ||
Groups.cluster_id, | ||
Scenes.cluster_id, | ||
OnOff.cluster_id, | ||
F000LevelControlCluster, | ||
TuyaZBExternalSwitchTypeCluster, | ||
], | ||
OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id], | ||
}, | ||
242: { | ||
PROFILE_ID: 41440, | ||
DEVICE_TYPE: 97, | ||
INPUT_CLUSTERS: [], | ||
OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id], | ||
}, | ||
}, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should probably move to
tuya.__init__.py
, but let's keep it here for now.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or first do the implementation in the quirk and getting it merged and its working OK and then making one more PR for moving it to tuya INIT then tuya INIT is little tricky with many addings and can needing rebasing many times and its PITA (i have one PR waiting for being merged to it).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that's the idea.
Keep here and merge. When required, move to Tuya INIT. 👍🏻