- 
                Notifications
    
You must be signed in to change notification settings  - Fork 902
 
Description
Problem description
Please add support for this device
Solution description
I have a solution that works with 2 problems, open reports as closed, and vice versa, and the Open percentage is wrong (inverted). It also reports as a blind not a curtain. This has been fixed in zigbee2mqtt (though via a PR for this bug). Koenkk/zigbee2mqtt#26660 (though it looks different physically).
The solution I have sort of working is copied from here
#3181
Screenshots/Video
Screenshots/Video
[Paste/upload your media here]
Diagnostics information
zha-01JCG94THYEBT9Z1T6PMA588B6-_TZE204_xu4a5rhj TS0601-8287b3420e8a965361165802b0f61827.json
Device signature
Device signature
{
  "node_descriptor": {
    "logical_type": 1,
    "complex_descriptor_available": 0,
    "user_descriptor_available": 0,
    "reserved": 0,
    "aps_flags": 0,
    "frequency_band": 8,
    "mac_capability_flags": 142,
    "manufacturer_code": 4417,
    "maximum_buffer_size": 66,
    "maximum_incoming_transfer_size": 66,
    "server_mask": 10752,
    "maximum_outgoing_transfer_size": 66,
    "descriptor_capability_field": 0
  },
  "endpoints": {
    "1": {
      "profile_id": "0x0104",
      "device_type": "0x0202",
      "input_clusters": [
        "0x0000",
        "0x0004",
        "0x0005",
        "0x0102",
        "0xef00"
      ],
      "output_clusters": [
        "0x000a",
        "0x0019"
      ]
    },
    "242": {
      "profile_id": "0xa1e0",
      "device_type": "0x0061",
      "input_clusters": [],
      "output_clusters": [
        "0x0021"
      ]
    }
  },
  "manufacturer": "_TZE204_xu4a5rhj",
  "model": "TS0601",
  "class": "tuya_curtain.TuyaCover0601_GP"
}
Logs
Logs
[Paste the logs here]Custom quirk
Custom quirk
This is copy pasted from the issue 3181 which has the problems I noted. ```python"""Tuya MCU based cover and blinds."""
from typing import Dict, Optional, Union
from zigpy.profiles import zha
import zigpy.types as t
from zigpy.zcl import foundation
from zigpy.zcl.clusters.closures import WindowCovering
from zigpy.zcl.clusters.general import (
Basic,
GreenPowerProxy,
Groups,
Ota,
Scenes,
Time,
)
from zhaquirks.const import (
DEVICE_TYPE,
ENDPOINTS,
INPUT_CLUSTERS,
MODELS_INFO,
OUTPUT_CLUSTERS,
PROFILE_ID,
)
from zhaquirks.tuya import (
NoManufacturerCluster,
TUYA_MCU_COMMAND,
TuyaLocalCluster,
TuyaWindowCover,
TuyaManufacturerWindowCover,
)
from zhaquirks.tuya.mcu import (
DPToAttributeMapping,
TuyaClusterData,
TuyaMCUCluster,
)
Maps OPEN/CLOSE/STOP cover commands from Tuya to Zigbee
https://github.com/zigpy/zigpy/blob/master/zigpy/zcl/clusters/closures.py#L558
https://developer.tuya.com/en/docs/iot-device-dev/zigbee-curtain-switch-access-standard?id=K9ik6zvra3twv#title-7-DP1%20and%20DP4%20Curtain%20switch%201%20and%202
class TuyaCC(t.enum8):
"""Tuya cover commands."""
OPEN = 0x00
STOP = 0x01
CLOSE = 0x02
class ZclCC(t.enum8):
"""ZCL cover commands."""
OPEN = 0x00
CLOSE = 0x01
STOP = 0x02
TUYA2ZB_COMMANDS = {
ZclCC.OPEN: TuyaCC.OPEN,
ZclCC.CLOSE: TuyaCC.CLOSE,
ZclCC.STOP: TuyaCC.STOP,
}
class TuyaWindowCovering(NoManufacturerCluster, WindowCovering, TuyaLocalCluster):
"""Tuya MCU WindowCovering cluster."""
"""Add additional attributes for direction"""
attributes = WindowCovering.attributes.copy()
attributes.update(
    {
        0xF000: ("curtain_switch", t.enum8, True),  # 0: open, 1: stop, 2: close
        0xF001: ("accurate_calibration", t.enum8, True),  # 0: calibration started, 1: calibration finished
        0xF002: ("motor_steering", t.enum8, True),  # 0: default, 1: reverse
        0xF003: ("travel", t.uint16_t, True),  # 30 to 9000 (units of 0.1 seconds)
    }
)
async def command(
    self,
    command_id: Union[foundation.GeneralCommand, 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."""
    # if manufacturer is None:
    #     manufacturer = self.endpoint.device.manufacturer
    self.debug(
        "Sending Tuya Cluster Command. Cluster Command is %x, Arguments are %s",
        command_id,
        args,
    )
    # (upopen, downclose, stop)
    if command_id in (0x0002, 0x0000, 0x0001):  # ?0x0003: continue?
        cluster_data = TuyaClusterData(
            endpoint_id=self.endpoint.endpoint_id,
            cluster_name=self.ep_attribute,
            cluster_attr="curtain_switch",
            attr_value=TUYA2ZB_COMMANDS[command_id],  # convert tuya2zigbee command
            expect_reply=expect_reply,
            manufacturer=-1,
        )
        self.endpoint.device.command_bus.listener_event(
            TUYA_MCU_COMMAND,
            cluster_data,
        )
        return foundation.GENERAL_COMMANDS[
            foundation.GeneralCommand.Default_Response
        ].schema(command_id=command_id, status=foundation.Status.SUCCESS)
    # (go_to_lift_percentage)
    elif command_id == 0x0005:
        lift_value = args[0]
        cluster_data = TuyaClusterData(
            endpoint_id=self.endpoint.endpoint_id,
            cluster_name=self.ep_attribute,
            cluster_attr="current_position_lift_percentage",
            attr_value=lift_value,
            expect_reply=expect_reply,
            manufacturer=-1,
        )
        self.endpoint.device.command_bus.listener_event(
            TUYA_MCU_COMMAND,
            cluster_data,
        )
        return foundation.GENERAL_COMMANDS[
            foundation.GeneralCommand.Default_Response
        ].schema(command_id=command_id, status=foundation.Status.SUCCESS)
    self.warning("Unsupported command_id: %s", command_id)
    return foundation.GENERAL_COMMANDS[
        foundation.GeneralCommand.Default_Response
    ].schema(command_id=command_id, status=foundation.Status.UNSUP_CLUSTER_COMMAND)
class TuyaWindowCoverManufCluster(TuyaMCUCluster):
"""Tuya with WindowCover data points."""
attributes = TuyaMCUCluster.attributes.copy()
attributes.update(
    {
        0x5000: ("backlight_mode", t.enum8, True),  # 0: off, 1: on
        0x8001: ("indicator_status", t.enum8, True),  # 0: status, 1: position, 2: off (?backlight_mode?)
    }
)
dp_to_attribute: Dict[int, DPToAttributeMapping] = {
    1: DPToAttributeMapping(
        TuyaWindowCovering.ep_attribute,
        "curtain_switch",
    ),
    2: DPToAttributeMapping(
        TuyaWindowCovering.ep_attribute,
        "current_position_lift_percentage",  # for Slider movement
        # dp_type=TuyaDPType.VALUE,
    ),
    3: DPToAttributeMapping(
        TuyaWindowCovering.ep_attribute,
        "current_position_lift_percentage",  # for Slider updates
    ),
}
data_point_handlers = {
    1: "_dp_2_attr_update",
    2: "_dp_2_attr_update",
    3: "_dp_2_attr_update",
}
class TuyaCover0601_GP(TuyaWindowCover):
"""Tuya blind controller device."""
signature = {
# "NodeDescriptor(
#     logical_type=<LogicalType.Router: 1>, complex_descriptor_available=0, user_descriptor_available=0,
#     reserved=0, aps_flags=0, frequency_band=<FrequencyBand.Freq2400MHz: 8>,
#     mac_capability_flags=<MACCapabilityFlags.AllocateAddress|RxOnWhenIdle|MainsPowered|FullFunctionDevice: 142>,
#     manufacturer_code=4417, maximum_buffer_size=66, maximum_incoming_transfer_size=66, server_mask=10752,
#     maximum_outgoing_transfer_size=66, descriptor_capability_field=<DescriptorCapability.NONE: 0>,
#     *allocate_address=True, *is_alternate_pan_coordinator=False, *is_coordinator=False, *is_end_device=False,
#     *is_full_function_device=True, *is_mains_powered=True, *is_receiver_on_when_idle=True, *is_router=True, *is_security_capable=False
# )
MODELS_INFO: [
("_TZE204_xu4a5rhj", "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,
TuyaWindowCoverManufCluster.cluster_id,
],
OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
},
242: {
PROFILE_ID: 41440,
DEVICE_TYPE: 97,
INPUT_CLUSTERS: [],
OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
},
},
}
replacement = {
    ENDPOINTS: {
        1: {
            DEVICE_TYPE: zha.DeviceType.WINDOW_COVERING_DEVICE,
            INPUT_CLUSTERS: [
                Basic.cluster_id,
                Groups.cluster_id,
                Scenes.cluster_id,
                TuyaWindowCoverManufCluster,
                TuyaWindowCovering,
            ],
            OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
        },
        242: {
            PROFILE_ID: 41440,
            DEVICE_TYPE: 97,
            INPUT_CLUSTERS: [],
            OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
        },
    }
}
</details>
### Additional information
Thanks for everything you guys are doing.