Skip to content

Please add common curtain motor TS0601 _TZE204_xu4a5rhj [Device Support Request] #4464

@dbw1966

Description

@dbw1966

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions