TUYA TS0601 Zigbee garage door opener

Hi @balu79 ,

Thanks for checking this out this extensively!
I tried adding my garage door opener via the quirk in the linked thread, however now it doesn’t add anything but link quality.
So I don’t have a switch, nor a contact sensor available.

Did you just use the code available in the thread or did you adapt something?

EDIT

Completely forgot that I disabled the use of ZHA Quirks in my configuration.yaml file.
Works now, will start testing again.

1 Like

I’m currently using Z2MQTT and for simplicity sake I would prefer to move over to ZHA at some point. This device is my only zigbee device that is preventing me from switching. I’ve not used or being exposed to ‘quirks’ before so forgive my noob questions:

Is a quirk an intermediate step before a device gets official support, or is this the end state for this device?

The light entity and the automation to reset puts me off, at the moment. I see that there’s a way to change the light to a switch, which is good. ([Device Support Request] - ZigbeeModel: TS0601, Zigbee Manufacturer: _TZE204_nklqjk62 · Issue #2635 · zigpy/zha-device-handlers · GitHub)

On the automation front, Z2MQTT also has it show as a switch that doesn’t auto-reset. I’m using the below code to create a cover entity successfully without any need for an automation, as the service such as switch.turn_on is callable even if a switch is already on. I’ve been running it this way for a year or so with no issue at all.

Would someone be able to test if this approach in ZHA avoids the need for an automation to reset the switch position?

cover:
  - platform: template
    covers:
      garage_door:
        device_class: garage
        friendly_name: "Garage Door"
        unique_id: garage_door_cover
        value_template: >-
          {% if is_state('binary_sensor.garage_door_garage_door_contact', 'on') %}
            open
          {% else %}
            closed
          {% endif %}
        open_cover:
          service: switch.turn_on
          target:
            entity_id: switch.garage_door_trigger
        close_cover:
          service: switch.turn_off
          target:
            entity_id: switch.garage_door_trigger
        stop_cover:
          service: switch.turn_off
          target:
            entity_id: switch.garage_door_trigger
1 Like

Hi I am a bit new on zha… sorry for this “stupid” question: in which file do you add your cover template ?
Thx

The easiest is too add directly it to the configuration.yaml file, also see the relevant documentation here: Template cover - Home Assistant

1 Like

I have it working similarly, just instead of turn_on and turn_off I simply use the switch.toggle service for all cases. Works fine for me, also without an additional automation.

1 Like

Thank you for this.

It took me some time but I have uninstalled Z2MQTT and am up and running in ZHA.

I had to try a few different quirks before I got it working, but I got there in the end.

Once I got my entity IDs to match, my cover template above worked IDENTICALLY to what I was using in Z2MQTT.

I’ve left mine as turn_on and turn_off as the toggle only works with the switch in a certain direction and I believe this could avoid the lift/lower controls behaving as stop etc.

Thanks again.

HI,

I have the _TZE200_wfxuhoea version. I add the quirks and I add the module to ZHA.
I think the quirks is detected because when I access the module configuration page, I have under zigbee informations: “Quirk:ts0601_garage.TuyaGarageSwitchTO”.

The problem is that I don’t have entities.

I tried to remove, restart and add again but I can see event fired history of the previous configuration

Related to the switch that should be a button situation: Using some button conditional cards and the horizontal stack card I can make something that looks and act more like a button with state information. This post helped me so I wanted to share.

When the Sensor Is open (new here so I can only post one picture but the closed is as you might expect):

type: horizontal-stack
cards:
  - type: conditional
    conditions:
      - condition: state
        entity: binary_sensor.garagedoor_new_opening
        state: 'off'
    card:
      show_name: true
      show_icon: true
      state_color: false
      type: button
      tap_action:
        action: toggle
      entity: switch.garagedoor_new_switch
      icon: mdi:garage-variant
      hold_action:
        action: none
      name: Garage Door (Closed)
  - type: conditional
    conditions:
      - condition: state
        entity: binary_sensor.garagedoor_new_opening
        state: 'on'
    card:
      show_name: true
      show_icon: true
      state_color: false
      type: button
      tap_action:
        action: toggle
      entity: switch.garagedoor_new_switch
      icon: mdi:garage-open-variant
      hold_action:
        action: none
      name: Garage Door (Open)

2 Likes

Same for me, only I’m using the current _TZE204_nklqjk62 version. I’m probably open to mistakes but I can’t see that I’ve done anything different to others that have got this working. I’m wondering if there is some other config, maybe in ZHA itself that is different.

I’m new here, and also posted across in Github my config/steps. Hopefully that’s OK practice?

1 Like

Rather than using conditionals, you can use a template cover so that you get an actual garage door entity with open/close state and up/down cover controls.

This requires no automation as the switch.turn_on can be used even if the switch is already in the on position (and vice versa).

Example of my card when open/closed using the mushroom cover card.

cover:
  - platform: template
    covers:
      garage_door:
        device_class: garage
        friendly_name: "Garage Door"
        unique_id: garage_door_cover
        value_template: >-
          {% if is_state('binary_sensor.garage_door_garage_door_contact', 'on') %}
            open
          {% else %}
            closed
          {% endif %}
        open_cover:
          service: switch.turn_on
          target:
            entity_id: switch.garage_door_trigger
        close_cover:
          service: switch.turn_off
          target:
            entity_id: switch.garage_door_trigger
        stop_cover:
          service: switch.turn_off
          target:
            entity_id: switch.garage_door_trigger
2 Likes

Hello @Illinoid, I know it’s been a long time since you posted your message.
I’m trying to unerstand if the “button” behaviour is a hardware dependent behaviour of this device, or if it is defined in the quirk.
I use this device for a swiming pool and I need a normal switch. So I’m trying to understand if I can modify the behaviour in the quirk or if it’s useless because anyway the hardware is acting only like a button.
Thank you :slight_smile:

Hello all,

I struggled all day with the integration of my Tuya TS0601 (version _TZE200_wfxuhoea) and I finally got it running.

For those who are struggling with it, I had to strictly apply Illinoid’s initial recommendations:

  • Path in configuration.yaml must be /config/your_folder
  • Filename must be ts0601_garage.py
  • Remove the device if it’s already been added
  • Reboot HA
  • Add the device again

Below is the Quirk I am using:

"""Tuya based cover and blinds."""
from typing import Dict

from zigpy.profiles import zha
from zigpy.quirks import CustomDevice
import zigpy.types as t
from zigpy.zcl.clusters.general import Basic, GreenPowerProxy, Groups, Ota, Scenes, Time
from zigpy.zcl.clusters.security import IasZone

from zhaquirks.const import (
    DEVICE_TYPE,
    ENDPOINTS,
    INPUT_CLUSTERS,
    MODELS_INFO,
    OUTPUT_CLUSTERS,
    PROFILE_ID,
)

from zhaquirks.tuya import TuyaLocalCluster, TuyaDPType
from zhaquirks.tuya.mcu import DPToAttributeMapping, TuyaMCUCluster, TuyaOnOff
from zhaquirks.tuya.ts0601_dimmer import TuyaOnOffNM

ZONE_TYPE = 0x0001

class ContactSwitchCluster(TuyaLocalCluster, IasZone):
    """Tuya ContactSwitch Sensor."""

    _CONSTANT_ATTRIBUTES = {ZONE_TYPE: IasZone.ZoneType.Contact_Switch}

    def _update_attribute(self, attrid, value):
        self.debug("_update_attribute '%s': %s", attrid, value)
        super()._update_attribute(attrid, value)


class TuyaGarageManufCluster(TuyaMCUCluster):
    """Tuya garage door opener."""

    attributes = TuyaMCUCluster.attributes.copy()
    attributes.update(
        {
            # ramdom attribute IDs
            0xEF02: ("dp_2", t.uint32_t, True),
            0xEF04: ("dp_4", t.uint32_t, True),
            0xEF05: ("dp_5", t.uint32_t, True),
            0xEF0B: ("dp_11", t.Bool, True),
            0xEF0C: ("dp_12", t.enum8, True),
        }
    )

    dp_to_attribute: Dict[int, DPToAttributeMapping] = {
        # garage door trigger ¿on movement, on open, on closed?
        1: DPToAttributeMapping(
            TuyaOnOffNM.ep_attribute,
            "on_off",
            TuyaDPType.BOOL,
        ),
        2: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "dp_2",
            TuyaDPType.VALUE,
        ),
        3: DPToAttributeMapping(
            ContactSwitchCluster.ep_attribute,
            "zone_status",
            #TuyaDPType.BOOL,
            lambda x: IasZone.ZoneStatus.Alarm_1 if x else 0,
            endpoint_id=2,
        ),
        4: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "dp_4",
            TuyaDPType.VALUE,
        ),
        5: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "dp_5",
            TuyaDPType.VALUE,
        ),
        11: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "dp_11",
            TuyaDPType.BOOL,
        ),
        # garage door status (open, closed, ...)
        12: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "dp_12",
            TuyaDPType.ENUM,
        ),
    }

    data_point_handlers = {
        1: "_dp_2_attr_update",
        2: "_dp_2_attr_update",
        3: "_dp_2_attr_update",
        4: "_dp_2_attr_update",
        5: "_dp_2_attr_update",
        11: "_dp_2_attr_update",
        12: "_dp_2_attr_update",
    }


class TuyaGarageSwitchTO(CustomDevice):
    """Tuya Garage switch."""

    signature = {
        MODELS_INFO: [
            ("_TZE200_wfxuhoea", "TS0601"),
        ],
        ENDPOINTS: {
            # <SimpleDescriptor endpoint=1 profile=260 device_type=0x0051
            # input_clusters=[0, 4, 5, 61184]
            # output_clusters=[10, 25]>
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    TuyaGarageManufCluster.cluster_id,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            # <SimpleDescriptor endpoint=242 profile=41440 device_type=97
            # input_clusters=[]
            # output_clusters=[33]
            242: {
                PROFILE_ID: 41440,
                DEVICE_TYPE: 97,
                INPUT_CLUSTERS: [],
                OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
            },
        },
    }

    replacement = {
        ENDPOINTS: {
            1: {
                DEVICE_TYPE: zha.DeviceType.ON_OFF_LIGHT,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    TuyaGarageManufCluster,
                    TuyaOnOffNM,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            2: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.IAS_ZONE,
                INPUT_CLUSTERS: [
                    ContactSwitchCluster
                ],
                OUTPUT_CLUSTERS: [],
            },
            242: {
                PROFILE_ID: 0xA1E0,
                DEVICE_TYPE: 0x0061,
                INPUT_CLUSTERS: [],
                OUTPUT_CLUSTERS: [0x0021],
            },
        },
    }

Also I’ve been able to follow alexeiw123 hints about Cover and I now am able to open/close my garage door using this card:

I just don’t know how to replace the default Volets text by a custom text.

Here is my cover config:

cover:
  - platform: template
    covers:
      garage_door:
        device_class: garage
        friendly_name: "Porte Garage"
        unique_id: garage_door_cover
        value_template: >-
          {% if is_state('binary_sensor.tze200_wfxuhoea_ts0601_ouverture', 'on') %}
            open
          {% else %}
            closed
          {% endif %}
        open_cover:
          service: light.turn_on
          target:
            entity_id: light.tze200_wfxuhoea_ts0601_lumiere
        close_cover:
          service: light.turn_off
          target:
            entity_id: light.tze200_wfxuhoea_ts0601_lumiere
        stop_cover:
          service: light.turn_off
          target:
            entity_id: light.tze200_wfxuhoea_ts0601_lumiere

Hope it helps someone :slightly_smiling_face:

1 Like

Happy to have been of help.

How come you’re using the light.turn_x service in your cover config? The entity name (lumiere) makes it seem as if this is actually a light? Or is this just odd naming for the trigger entity? This is usually where you’d be triggering the door to open/close with a switch. I’d be looking to fix this part as it’s pretty easy for assistants etc. to group light actions together and you don’t want your garage door to accidentally open when you don’t want it to.

        open_cover:
          service: light.turn_on
          target:
            entity_id: light.tze200_wfxuhoea_ts0601_lumiere

oh and this just looks like the lovelace card you’re using, rather than the cover entity…

That’s correct, using the quirk attached to my previous post the equipment is detected as a light and I don’t understand why:

I have worked with quite some systems over the years but I am new to HA: do you think I am doing something wrong?

As for the cover, at first I tried to implement yours as is but it didn’t work.
I supposed that I could replace the switch keyword by the light one… and it worked but apparently not everything is ok :sweat_smile:

So I haven’t selected the correct card?

I can see the light variable in the States tab:

Here is the quirk that I used with success. Have a look at the parts in yours that reference light and compare to mine that reference switch.

Note that a) I’m on mobile right now and b) I went back to Z2MQTT not long after this for other reasons, so there may be typos, and I haven’t been following closely.

from typing import Dict
from zigpy.profiles import zha
from zigpy.quirks import CustomDevice
import zigpy.types as t
from zigpy.zcl.clusters.general import Basic, GreenPowerProxy, Groups, Ota, Scenes, Time
from zigpy.zcl.clusters.security import IasZone

from zhaquirks.const import (
    DEVICE_TYPE,
    ENDPOINTS,
    INPUT_CLUSTERS,
    MODELS_INFO,
    OUTPUT_CLUSTERS,
    PROFILE_ID,
)
from zhaquirks.tuya import TuyaLocalCluster
from zhaquirks.tuya.mcu import (
    TuyaMCUCluster,
    TuyaOnOff,
    DPToAttributeMapping

)

#    TuyaDPType,


from zhaquirks.tuya.mcu import ( TuyaMCUCluster
)

from zhaquirks.tuya.ts0601_dimmer import TuyaOnOffNM


ZONE_TYPE = 0x0001


class ContactSwitchCluster(TuyaLocalCluster, IasZone):
    """Tuya ContactSwitch Sensor."""

    _CONSTANT_ATTRIBUTES = {ZONE_TYPE: IasZone.ZoneType.Contact_Switch}

    def _update_attribute(self, attrid, value):
        self.debug("_update_attribute '%s': %s", attrid, value)
        super()._update_attribute(attrid, value)


class TuyaGarageManufCluster(TuyaMCUCluster):
    """Tuya garage door opener."""

    attributes = TuyaMCUCluster.attributes.copy()
    attributes.update(
        {
            # ramdom attribute IDs
            0xEF02: ("dp_2", t.uint32_t, True),
            0xEF04: ("dp_4", t.uint32_t, True),
            0xEF05: ("dp_5", t.uint32_t, True),
            0xEF0B: ("dp_11", t.Bool, True),
            0xEF0C: ("dp_12", t.enum8, True),
        }
    )

    dp_to_attribute: Dict[int, DPToAttributeMapping] = {
        # garage door trigger ¿on movement, on open, on closed?
        1: DPToAttributeMapping(
            TuyaOnOffNM.ep_attribute,
            "on_off",
            #dp_type=TuyaDPType.BOOL,
        ),
        2: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "dp_2",
            #dp_type=TuyaDPType.VALUE,
        ),
        3: DPToAttributeMapping(
            ContactSwitchCluster.ep_attribute,
            "zone_status",
            #dp_type=TuyaDPType.BOOL,
            converter=lambda x: IasZone.ZoneStatus.Alarm_1 if x else 0,
            endpoint_id=2,
        ),
        4: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "dp_4",
            #dp_type=TuyaDPType.VALUE,
        ),
        5: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "dp_5",
            #dp_type=TuyaDPType.VALUE,
        ),
        11: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "dp_11",
            #dp_type=TuyaDPType.BOOL,
        ),
        # garage door status (open, closed, ...)
        12: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "dp_12",
            #dp_type=TuyaDPType.ENUM,
        ),
    }

    data_point_handlers = {
        1: "_dp_2_attr_update",
        2: "_dp_2_attr_update",
        3: "_dp_2_attr_update",
        4: "_dp_2_attr_update",
        5: "_dp_2_attr_update",
        11: "_dp_2_attr_update",
        12: "_dp_2_attr_update",
    }


class TuyaGarageSwitchTO(CustomDevice):
    """Tuya Garage switch."""

    signature = {
        MODELS_INFO: [
            ("_TZE200_nklqjk62", "TS0601"),
        ],
        ENDPOINTS: {
            # <SimpleDescriptor endpoint=1 profile=260 device_type=0x0051
            # input_clusters=[0, 4, 5, 61184]
            # output_clusters=[10, 25]>
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    TuyaGarageManufCluster.cluster_id,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            # <SimpleDescriptor endpoint=242 profile=41440 device_type=97
            # input_clusters=[]
            # output_clusters=[33]
            242: {
                PROFILE_ID: 41440,
                DEVICE_TYPE: 97,
                INPUT_CLUSTERS: [],
                OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
            },
        },
    }

    replacement = {
        ENDPOINTS: {
            1: {
                DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    TuyaGarageManufCluster,
                    TuyaOnOffNM,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            2: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.IAS_ZONE,
                INPUT_CLUSTERS: [
                    ContactSwitchCluster
                ],
                OUTPUT_CLUSTERS: [],
            },
            242: {
                PROFILE_ID: 0xA1E0,
                DEVICE_TYPE: 0x0061,
                INPUT_CLUSTERS: [],
                OUTPUT_CLUSTERS: [0x0021],
            },
        },
    }

Just that it doesn’t mean anything is wrong with the cover. In your dashboard you can edit the page, look at that card and edit it in yaml, you should have somewhere that you can name it whatever you please.

hi all,

I have been struggling with this now for weeks, I have a TZE200_wfxuhoea and just cant seem to get this quirk working.
I made a folder in config called ‘zhaquirks’ with a file inside called ‘ts0601_garage.py’ and added ‘custom_quirks_path: /config/zhaquirks’
Home assistant seems to be using the quirk when i add the device but i still cant see anything usable.

If anyone is willing to remote desktop and sort this for me im happy to pay for a few beers for your time… this is the last device to get onto my HA so i can get rid of the Tuya zigbee gateway.

Hello martyn,

Here is my configuration.yaml lines related to quirks:


# Loads default set of integrations. Do not remove.
default_config:

zha:
  enable_quirks: true
  custom_quirks_path: /config/zha_quirks/

Maybe you juste have to enable quirks?

Also, here is the latest version of my quirk, based on alexeiw123’s one:

"""Tuya based cover and blinds."""
from typing import Dict

from zigpy.profiles import zha
from zigpy.quirks import CustomDevice
import zigpy.types as t
from zigpy.zcl.clusters.general import Basic, GreenPowerProxy, Groups, Ota, Scenes, Time
from zigpy.zcl.clusters.security import IasZone

from zhaquirks.const import (
    DEVICE_TYPE,
    ENDPOINTS,
    INPUT_CLUSTERS,
    MODELS_INFO,
    OUTPUT_CLUSTERS,
    PROFILE_ID,
)

from zhaquirks.tuya import TuyaLocalCluster, TuyaDPType
from zhaquirks.tuya.mcu import DPToAttributeMapping, TuyaMCUCluster, TuyaOnOff
from zhaquirks.tuya.ts0601_dimmer import TuyaOnOffNM

ZONE_TYPE = 0x0001

class ContactSwitchCluster(TuyaLocalCluster, IasZone):
    """Tuya ContactSwitch Sensor."""

    _CONSTANT_ATTRIBUTES = {ZONE_TYPE: IasZone.ZoneType.Contact_Switch}

    def _update_attribute(self, attrid, value):
        self.debug("_update_attribute '%s': %s", attrid, value)
        super()._update_attribute(attrid, value)


class TuyaGarageManufCluster(TuyaMCUCluster):
    """Tuya garage door opener."""

    attributes = TuyaMCUCluster.attributes.copy()
    attributes.update(
        {
            # ramdom attribute IDs
            0xEF02: ("dp_2", t.uint32_t, True),
            0xEF04: ("dp_4", t.uint32_t, True),
            0xEF05: ("dp_5", t.uint32_t, True),
            0xEF0B: ("dp_11", t.Bool, True),
            0xEF0C: ("dp_12", t.enum8, True),
        }
    )

    dp_to_attribute: Dict[int, DPToAttributeMapping] = {
        # garage door trigger ¿on movement, on open, on closed?
        1: DPToAttributeMapping(
            TuyaOnOffNM.ep_attribute,
            "on_off",
            #dp_type=TuyaDPType.BOOL,
        ),
        2: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "dp_2",
            #dp_type=TuyaDPType.VALUE,
        ),
        3: DPToAttributeMapping(
            ContactSwitchCluster.ep_attribute,
            "zone_status",
            #dp_type=TuyaDPType.BOOL,
            converter=lambda x: IasZone.ZoneStatus.Alarm_1 if x else 0,
            endpoint_id=2,
        ),
        4: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "dp_4",
            #dp_type=TuyaDPType.VALUE,
        ),
        5: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "dp_5",
            #dp_type=TuyaDPType.VALUE,
        ),
        11: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "dp_11",
            #dp_type=TuyaDPType.BOOL,
        ),
        # garage door status (open, closed, ...)
        12: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "dp_12",
            #dp_type=TuyaDPType.ENUM,
        ),
    }

    data_point_handlers = {
        1: "_dp_2_attr_update",
        2: "_dp_2_attr_update",
        3: "_dp_2_attr_update",
        4: "_dp_2_attr_update",
        5: "_dp_2_attr_update",
        11: "_dp_2_attr_update",
        12: "_dp_2_attr_update",
    }


class TuyaGarageSwitchTO(CustomDevice):
    """Tuya Garage switch."""

    signature = {
        MODELS_INFO: [
            ("_TZE200_wfxuhoea", "TS0601"),
        ],
        ENDPOINTS: {
            # <SimpleDescriptor endpoint=1 profile=260 device_type=0x0051
            # input_clusters=[0, 4, 5, 61184]
            # output_clusters=[10, 25]>
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    TuyaGarageManufCluster.cluster_id,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            # <SimpleDescriptor endpoint=242 profile=41440 device_type=97
            # input_clusters=[]
            # output_clusters=[33]
            242: {
                PROFILE_ID: 41440,
                DEVICE_TYPE: 97,
                INPUT_CLUSTERS: [],
                OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
            },
        },
    }

    replacement = {
        ENDPOINTS: {
            1: {
                DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    TuyaGarageManufCluster,
                    TuyaOnOffNM,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            2: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.IAS_ZONE,
                INPUT_CLUSTERS: [
                    ContactSwitchCluster
                ],
                OUTPUT_CLUSTERS: [],
            },
            242: {
                PROFILE_ID: 0xA1E0,
                DEVICE_TYPE: 0x0061,
                INPUT_CLUSTERS: [],
                OUTPUT_CLUSTERS: [0x0021],
            },
        },
    }

Can you try this one?

@alexeiw123: now with your quirk the equipment is detected as a switch, that’s a win :partying_face:

I can now add a cover to the dashboard that opens/closes the door with the up/down buttons, however the state is never dispayed.

I have tried using the Garage Door example in this configuration guide: Template cover - Home Assistant

But this time the icon never updates…

cover:
  - platform: template
    covers:
      garage_door:
        device_class: garage
        friendly_name: "Porte Garage"
        position_template: "{{ states('binary_sensor.porte_garage_ouverture') }}"
        open_cover:
          - condition: state
            entity_id: binary_sensor.porte_garage_ouverture
            state: "off"
          - service: switch.turn_on
            target:
              entity_id: switch.porte_garage_commutateur
        close_cover:
          - condition: state
            entity_id: binary_sensor.porte_garage_ouverture
            state: "on"
          - service: switch.turn_off
            target:
              entity_id: switch.porte_garage_commutateur
        stop_cover:
          service: switch.turn_on
          target:
            entity_id: switch.porte_garage_commutateur
        icon_template: >-
          {% if states('binary_sensor.porte_garage_ouverture')|float > 0 %}
            mdi:garage-open
          {% else %}
            mdi:garage
          {% endif %}

I certainly am doing something wrong but I don’t know what exactly.

1 Like

Hello future readers and in particular @aussiebish

If you’re using the _TZE204_nklqjk62 model then use the following for your ‘ts0601_garage.py’ file:

"""Tuya based cover and blinds."""
from typing import Dict

from zigpy.profiles import zha
from zigpy.quirks import CustomDevice
import zigpy.types as t
from zigpy.zcl.clusters.general import Basic, GreenPowerProxy, Groups, Ota, Scenes, Time
from zigpy.zcl.clusters.security import IasZone

from zhaquirks.const import (
    DEVICE_TYPE,
    ENDPOINTS,
    INPUT_CLUSTERS,
    MODELS_INFO,
    OUTPUT_CLUSTERS,
    PROFILE_ID,
)

from zhaquirks.tuya import TuyaLocalCluster, TuyaDPType
from zhaquirks.tuya.mcu import DPToAttributeMapping, TuyaMCUCluster, TuyaOnOff
from zhaquirks.tuya.ts0601_dimmer import TuyaOnOffNM

ZONE_TYPE = 0x0001

class ContactSwitchCluster(TuyaLocalCluster, IasZone):
    """Tuya ContactSwitch Sensor."""

    _CONSTANT_ATTRIBUTES = {ZONE_TYPE: IasZone.ZoneType.Contact_Switch}

    def _update_attribute(self, attrid, value):
        self.debug("_update_attribute '%s': %s", attrid, value)
        super()._update_attribute(attrid, value)


class TuyaGarageManufCluster(TuyaMCUCluster):
    """Tuya garage door opener."""

    attributes = TuyaMCUCluster.attributes.copy()
    attributes.update(
        {
            # ramdom attribute IDs
            0xEF02: ("dp_2", t.uint32_t, True),
            0xEF04: ("dp_4", t.uint32_t, True),
            0xEF05: ("dp_5", t.uint32_t, True),
            0xEF0B: ("dp_11", t.Bool, True),
            0xEF0C: ("dp_12", t.enum8, True),
        }
    )

    dp_to_attribute: Dict[int, DPToAttributeMapping] = {
        # garage door trigger ¿on movement, on open, on closed?
        1: DPToAttributeMapping(
            TuyaOnOffNM.ep_attribute,
            "on_off",
            TuyaDPType.BOOL,
        ),
        2: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "dp_2",
            TuyaDPType.VALUE,
        ),
        3: DPToAttributeMapping(
            ContactSwitchCluster.ep_attribute,
            "zone_status",
            #TuyaDPType.BOOL,
            lambda x: IasZone.ZoneStatus.Alarm_1 if x else 0,
            endpoint_id=2,
        ),
        4: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "dp_4",
            TuyaDPType.VALUE,
        ),
        5: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "dp_5",
            TuyaDPType.VALUE,
        ),
        11: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "dp_11",
            TuyaDPType.BOOL,
        ),
        # garage door status (open, closed, ...)
        12: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "dp_12",
            TuyaDPType.ENUM,
        ),
    }

    data_point_handlers = {
        1: "_dp_2_attr_update",
        2: "_dp_2_attr_update",
        3: "_dp_2_attr_update",
        4: "_dp_2_attr_update",
        5: "_dp_2_attr_update",
        11: "_dp_2_attr_update",
        12: "_dp_2_attr_update",
    }


class TuyaGarageSwitchTO(CustomDevice):
    """Tuya Garage switch."""

    signature = {
        MODELS_INFO: [
            ("_TZE204_nklqjk62", "TS0601"),
        ],
        ENDPOINTS: {
            # <SimpleDescriptor endpoint=1 profile=260 device_type=0x0051
            # input_clusters=[0, 4, 5, 61184]
            # output_clusters=[10, 25]>
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    TuyaGarageManufCluster.cluster_id,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            # <SimpleDescriptor endpoint=242 profile=41440 device_type=97
            # input_clusters=[]
            # output_clusters=[33]
            242: {
                PROFILE_ID: 41440,
                DEVICE_TYPE: 97,
                INPUT_CLUSTERS: [],
                OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
            },
        },
    }

    replacement = {
        ENDPOINTS: {
            1: {
                DEVICE_TYPE: zha.DeviceType.ON_OFF_LIGHT,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    TuyaGarageManufCluster,
                    TuyaOnOffNM,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            2: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.IAS_ZONE,
                INPUT_CLUSTERS: [
                    ContactSwitchCluster
                ],
                OUTPUT_CLUSTERS: [],
            },
            242: {
                PROFILE_ID: 0xA1E0,
                DEVICE_TYPE: 0x0061,
                INPUT_CLUSTERS: [],
                OUTPUT_CLUSTERS: [0x0021],
            },
        },
    }
1 Like