Skip to content
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

Added Quirks for TS0601 _TZE200_fzo2pocs #801

Merged
merged 10 commits into from
Mar 12, 2021
149 changes: 149 additions & 0 deletions zhaquirks/tuya/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from zigpy.quirks import CustomCluster, CustomDevice
import zigpy.types as t
from zigpy.zcl import foundation
from zigpy.zcl.clusters.closures import WindowCovering
from zigpy.zcl.clusters.general import OnOff, PowerConfiguration
from zigpy.zcl.clusters.hvac import Thermostat, UserInterface

Expand All @@ -18,10 +19,26 @@
TUYA_SET_DATA_RESPONSE = 0x0002
TUYA_SET_TIME = 0x0024

COVER_EVENT = "cover_event"
SWITCH_EVENT = "switch_event"
ATTR_ON_OFF = 0x0000
ATTR_COVER_POSITION = 0x0008
TUYA_CMD_BASE = 0x0100

# Tuya cover command
# https://github.com/Koenkk/zigbee-herdsman-converters/issues/1159#issuecomment-614659802
# 0x2: open
# 0x1: stop
# 0x0: close
# maps to
# 0x0000: ("up_open", (), False),
# 0x0001: ("down_close", (), False),
# 0x0002: ("stop", (), False),

TUYA_COVER_COMMAND = {0x0000: 0x0000, 0x0001: 0x0002, 0x0002: 0x0001}

blauret marked this conversation as resolved.
Show resolved Hide resolved
TUYA_SET_COVER_POSITION_COMMAND = 0x0002

_LOGGER = logging.getLogger(__name__)


Expand Down Expand Up @@ -547,3 +564,135 @@ def handle_cluster_request(
self.listener_event(
ZHA_SEND_EVENT, self.press_type.get(press_type, "unknown"), []
)


class TuyaManufacturerWindowCover(TuyaManufCluster):
"""Manufacturer Specific Cluster for cover device."""

def handle_cluster_request(
self,
hdr: foundation.ZCLHeader,
args: Tuple[TuyaManufCluster.Command],
*,
dst_addressing: Optional[
Union[t.Addressing.Group, t.Addressing.IEEE, t.Addressing.NWK]
] = None,
) -> None:
"""Handle cluster request."""
tuya_payload = args[0]
blauret marked this conversation as resolved.
Show resolved Hide resolved

_LOGGER.debug(
"%s Received Attribute Report. Command is %x, Tuya Paylod values"
"[Status : %s, TSN: %s, Command: %s, Function: %s, Data: %s]",
self.endpoint.device.ieee,
hdr.command_id,
tuya_payload.status,
tuya_payload.tsn,
tuya_payload.command_id,
tuya_payload.function,
tuya_payload.data,
)

if hdr.command_id == 0x0002:
self.endpoint.device.cover_bus.listener_event(
COVER_EVENT,
tuya_payload.command_id,
tuya_payload.data,
)


class TuyaWindowCoverControl(LocalDataCluster, WindowCovering):
"""Manufacturer Specific Cluster of Device cover."""

def __init__(self, *args, **kwargs):
"""Initialize instance."""
super().__init__(*args, **kwargs)
self.endpoint.device.cover_bus.add_listener(self)

def cover_event(self, command, value):
blauret marked this conversation as resolved.
Show resolved Hide resolved
"""Cover event. update the position."""
if value[0] == 4:
position = 100 - value[4]
self._update_attribute(ATTR_COVER_POSITION, position)

def command(
blauret marked this conversation as resolved.
Show resolved Hide resolved
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."""

_LOGGER.debug(
"%s Sending Tuya Cluster Command.. Cluster Command is %x, Arguments are %s",
self.endpoint.device.ieee,
command_id,
args,
)
tuya_payload = TuyaManufCluster.Command()
# Open Close or Stop commands
if command_id in (0x0000, 0x0001, 0x0002):
tuya_payload.status = 0
tuya_payload.tsn = 0
tuya_payload.command_id = TUYA_CMD_BASE + self.endpoint.endpoint_id
tuya_payload.function = 0
tuya_payload.data = [
1,
TUYA_COVER_COMMAND[command_id],
] # remap the command to the Tuya command
# Set Position Command
elif command_id == 0x0005:
tuya_payload = TuyaManufCluster.Command()
tuya_payload.status = 0
tuya_payload.tsn = 0
tuya_payload.command_id = TUYA_SET_COVER_POSITION_COMMAND
tuya_payload.function = 0
position = args[0]
tuya_payload.data = [
4,
0,
0,
0,
100 - position,
] # Custom Command

elif command_id == 0x0006:
tuya_payload = TuyaManufCluster.Command()
tuya_payload.status = args[0]
tuya_payload.tsn = args[1]
tuya_payload.command_id = args[2]
tuya_payload.function = args[3]
tuya_payload.data = args[4]
else:
tuya_payload = None

if tuya_payload.command_id:
_LOGGER.debug(
"%s Sending Tuya Command. Paylod values [endpoint_id : %s, "
"Status : %s, TSN: %s, Command: %s, Function: %s, Data: %s]",
self.endpoint.device.ieee,
self.endpoint.endpoint_id,
tuya_payload.status,
tuya_payload.tsn,
tuya_payload.command_id,
tuya_payload.function,
tuya_payload.data,
)

return self.endpoint.tuya_manufacturer.command(
TUYA_SET_DATA, tuya_payload, expect_reply=True
)
else:
_LOGGER.debug("Unrecognised command: %x", command_id)
return foundation.Status.UNSUP_CLUSTER_COMMAND


class TuyaWindowCover(CustomDevice):
"""Tuya switch device."""

def __init__(self, *args, **kwargs):
"""Init device."""
self.cover_bus = Bus()
super().__init__(*args, **kwargs)
107 changes: 106 additions & 1 deletion zhaquirks/tuya/ts0601.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,15 @@
OUTPUT_CLUSTERS,
PROFILE_ID,
)
from ..tuya import TuyaManufacturerClusterOnOff, TuyaManufCluster, TuyaOnOff, TuyaSwitch
from ..tuya import (
TuyaManufacturerClusterOnOff,
TuyaManufacturerWindowCover,
TuyaManufCluster,
TuyaOnOff,
TuyaSwitch,
TuyaWindowCover,
TuyaWindowCoverControl,
)


class TuyaSingleSwitch(TuyaSwitch):
Expand Down Expand Up @@ -55,3 +63,100 @@ class TuyaSingleSwitch(TuyaSwitch):
}
}
}


class TuyaZemismartSmartCover0601(TuyaWindowCover):
"""Tuya Zemismart blind cover motor."""

signature = {
# "node_descriptor": "<NodeDescriptor byte1=1 byte2=64 mac_capability_flags=142 manufacturer_code=4098
# maximum_buffer_size=82 maximum_incoming_transfer_size=82 server_mask=11264
# maximum_outgoing_transfer_size=82 descriptor_capability_field=0>",
# input_clusters=[0x0000, 0x0004, 0x0005, 0x000a, 0xef00]
# output_clusters=[0x0019]
# <SimpleDescriptor endpoint=1 profile=260 device_type=51 input_clusters=[0, 4, 5, 61184] output_clusters=[25]>
MODELS_INFO: [
("_TZE200_fzo2pocs", "TS0601"),
],
ENDPOINTS: {
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
INPUT_CLUSTERS: [
Basic.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
Time.cluster_id,
TuyaManufCluster.cluster_id,
],
OUTPUT_CLUSTERS: [Ota.cluster_id],
},
},
}
replacement = {
ENDPOINTS: {
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.WINDOW_COVERING_DEVICE,
INPUT_CLUSTERS: [
Basic.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
Time.cluster_id,
TuyaManufacturerWindowCover,
TuyaWindowCoverControl,
],
OUTPUT_CLUSTERS: [Ota.cluster_id],
},
},
}


class TuyaMoesCover0601(TuyaWindowCover):
"""Tuya blind controller device."""

signature = {
# "node_descriptor": "NodeDescriptor(byte1=2, byte2=64, mac_capability_flags=128, manufacturer_code=4098,
# maximum_buffer_size=82, maximum_incoming_transfer_size=82, server_mask=11264,
# maximum_outgoing_transfer_size=82, descriptor_capability_field=0)",
# "endpoints": {
# "1": { "profile_id": 260, "device_type": "0x0051", "in_clusters": [ "0x0000", "0x0004","0x0005","0xef00"], "out_clusters": ["0x000a","0x0019"] }
# },
# "manufacturer": "_TZE200_zah67ekd",
# "model": "TS0601",
# "class": "zigpy.device.Device"
# }
MODELS_INFO: [
("_TZE200_zah67ekd", "TS0601"),
("_TZE200_xuzcvlku", "TS0601"),
], # Not tested
ENDPOINTS: {
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
INPUT_CLUSTERS: [
Basic.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
TuyaManufCluster.cluster_id,
],
OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
}
},
}

replacement = {
ENDPOINTS: {
1: {
DEVICE_TYPE: zha.DeviceType.WINDOW_COVERING_DEVICE,
INPUT_CLUSTERS: [
Basic.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
TuyaManufacturerWindowCover,
TuyaWindowCoverControl,
],
OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
}
}
}