zigpy / zha-device-handlers

ZHA device handlers bridge the functionality gap created when manufacturers deviate from the ZCL specification, handling deviations and exceptions by parsing custom messages to and from Zigbee devices.
Apache License 2.0
719 stars 663 forks source link

[Device Support Request] TS0603 by _TZE608_c75zqghm #3263

Open G4LLY opened 1 month ago

G4LLY commented 1 month ago

Problem description

Hello, I get this garage door opener from Aliexpress and once connected to ZHA, is does not show the proper command / informations.

Solution description

As a door opener it should have an open-close command and a sensor to show the door state (open / close)

Screenshots/Video

Screenshots/Video ![image](https://github.com/user-attachments/assets/51b5a8d0-2a9a-4fac-94ae-0ce4dbab62c6)

Device signature

Device signature ```json { "node_descriptor": "NodeDescriptor(logical_type=, complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=, mac_capability_flags=, manufacturer_code=4417, maximum_buffer_size=66, maximum_incoming_transfer_size=66, server_mask=10752, maximum_outgoing_transfer_size=66, descriptor_capability_field=, *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)", "endpoints": { "1": { "profile_id": "0x0104", "device_type": "0x0051", "input_clusters": [ "0x0000", "0x0003", "0x0004", "0x0005", "0xef00" ], "output_clusters": [ "0x000a", "0x0019" ] }, "242": { "profile_id": "0xa1e0", "device_type": "0x0061", "input_clusters": [], "output_clusters": [ "0x0021" ] } }, "manufacturer": "_TZE608_c75zqghm", "model": "TS0603", "class": "zigpy.device.Device" } ```

Diagnostic information

Diagnostic information ```json { "home_assistant": { "installation_type": "Home Assistant OS", "version": "2024.7.2", "dev": false, "hassio": true, "virtualenv": false, "python_version": "3.12.4", "docker": true, "arch": "x86_64", "timezone": "Europe/Brussels", "os_name": "Linux", "os_version": "6.6.33-haos", "supervisor": "2024.06.2", "host_os": "Home Assistant OS 12.4", "docker_version": "26.1.4", "chassis": "vm", "run_as_root": true }, "custom_components": { "hacs": { "documentation": "https://hacs.xyz/docs/configuration/start", "version": "1.34.0", "requirements": [ "aiogithubapi>=22.10.1" ] }, "tapo": { "documentation": "https://github.com/petretiandrea/home-assistant-tapo-p100", "version": "3.1.2", "requirements": [ "plugp100==5.1.3" ] }, "easee": { "documentation": "https://github.com/nordicopen/easee_hass", "version": "0.9.59", "requirements": [ "pyeasee==0.8.1" ] }, "tapo_control": { "documentation": "https://github.com/JurajNyiri/HomeAssistant-Tapo-Control", "version": "5.4.26", "requirements": [ "pytapo==3.3.23" ] }, "aarlo": { "documentation": "https://github.com/twrecked/hass-aarlo/blob/master/README.md", "version": "0.8.1.4", "requirements": [ "unidecode", "pyaarlo>=0.8.0.7" ] } }, "integration_manifest": { "domain": "zha", "name": "Zigbee Home Automation", "after_dependencies": [ "onboarding", "usb" ], "codeowners": [ "dmulcahey", "adminiuga", "puddly", "TheJulianJES" ], "config_flow": true, "dependencies": [ "file_upload" ], "documentation": "https://www.home-assistant.io/integrations/zha", "iot_class": "local_polling", "loggers": [ "aiosqlite", "bellows", "crccheck", "pure_pcapy3", "zhaquirks", "zigpy", "zigpy_deconz", "zigpy_xbee", "zigpy_zigate", "zigpy_znp", "universal_silabs_flasher" ], "requirements": [ "bellows==0.39.1", "pyserial==3.5", "zha-quirks==0.0.117", "zigpy-deconz==0.23.2", "zigpy==0.64.1", "zigpy-xbee==0.20.1", "zigpy-zigate==0.12.1", "zigpy-znp==0.12.2", "universal-silabs-flasher==0.0.20", "pyserial-asyncio-fast==0.11" ], "usb": [ { "vid": "10C4", "pid": "EA60", "description": "*2652*", "known_devices": [ "slae.sh cc2652rb stick" ] }, { "vid": "10C4", "pid": "EA60", "description": "*slzb-07*", "known_devices": [ "smlight slzb-07" ] }, { "vid": "1A86", "pid": "55D4", "description": "*sonoff*plus*", "known_devices": [ "sonoff zigbee dongle plus v2" ] }, { "vid": "10C4", "pid": "EA60", "description": "*sonoff*plus*", "known_devices": [ "sonoff zigbee dongle plus" ] }, { "vid": "10C4", "pid": "EA60", "description": "*tubeszb*", "known_devices": [ "TubesZB Coordinator" ] }, { "vid": "1A86", "pid": "7523", "description": "*tubeszb*", "known_devices": [ "TubesZB Coordinator" ] }, { "vid": "1A86", "pid": "7523", "description": "*zigstar*", "known_devices": [ "ZigStar Coordinators" ] }, { "vid": "1CF1", "pid": "0030", "description": "*conbee*", "known_devices": [ "Conbee II" ] }, { "vid": "0403", "pid": "6015", "description": "*conbee*", "known_devices": [ "Conbee III" ] }, { "vid": "10C4", "pid": "8A2A", "description": "*zigbee*", "known_devices": [ "Nortek HUSBZB-1" ] }, { "vid": "0403", "pid": "6015", "description": "*zigate*", "known_devices": [ "ZiGate+" ] }, { "vid": "10C4", "pid": "EA60", "description": "*zigate*", "known_devices": [ "ZiGate" ] }, { "vid": "10C4", "pid": "8B34", "description": "*bv 2010/10*", "known_devices": [ "Bitron Video AV2010/10" ] } ], "zeroconf": [ { "type": "_esphomelib._tcp.local.", "name": "tube*" }, { "type": "_zigate-zigbee-gateway._tcp.local.", "name": "*zigate*" }, { "type": "_zigstar_gw._tcp.local.", "name": "*zigstar*" }, { "type": "_uzg-01._tcp.local.", "name": "uzg-01*" }, { "type": "_slzb-06._tcp.local.", "name": "slzb-06*" }, { "type": "_xzg._tcp.local.", "name": "xzg*" }, { "type": "_czc._tcp.local.", "name": "czc*" } ], "is_built_in": true }, "setup_times": { "null": { "setup": 0.00010956400001305155 }, "01J2VYW0Q4CK22WV107ZBJ5FJZ": { "wait_import_platforms": -0.0002739289993769489, "config_entry_setup": 15.528421514998627 } }, "data": { "ieee": "**REDACTED**", "nwk": 10239, "manufacturer": "_TZE608_c75zqghm", "model": "TS0603", "name": "_TZE608_c75zqghm TS0603", "quirk_applied": false, "quirk_class": "zigpy.device.Device", "quirk_id": null, "manufacturer_code": 4417, "power_source": "Mains", "lqi": 255, "rssi": -36, "last_seen": "2024-07-18T14:08:40", "available": true, "device_type": "Router", "signature": { "node_descriptor": "NodeDescriptor(logical_type=, complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=, mac_capability_flags=, manufacturer_code=4417, maximum_buffer_size=66, maximum_incoming_transfer_size=66, server_mask=10752, maximum_outgoing_transfer_size=66, descriptor_capability_field=, *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)", "endpoints": { "1": { "profile_id": "0x0104", "device_type": "0x0051", "input_clusters": [ "0x0000", "0x0003", "0x0004", "0x0005", "0xef00" ], "output_clusters": [ "0x000a", "0x0019" ] }, "242": { "profile_id": "0xa1e0", "device_type": "0x0061", "input_clusters": [], "output_clusters": [ "0x0021" ] } }, "manufacturer": "_TZE608_c75zqghm", "model": "TS0603" }, "active_coordinator": false, "entities": [ { "entity_id": "button.tze608_c75zqghm_ts0603_identifier", "name": "_TZE608_c75zqghm TS0603" }, { "entity_id": "update.tze608_c75zqghm_ts0603_micrologiciel", "name": "_TZE608_c75zqghm TS0603" } ], "neighbors": [], "routes": [], "endpoint_names": [ { "name": "SMART_PLUG" }, { "name": "PROXY_BASIC" } ], "user_given_name": null, "device_reg_id": "732c438dedfedfd781d9a2499c62b5ce", "area_id": "garden", "cluster_details": { "1": { "device_type": { "name": "SMART_PLUG", "id": 81 }, "profile_id": 260, "in_clusters": { "0x0000": { "endpoint_attribute": "basic", "attributes": { "0x0004": { "attribute_name": "manufacturer", "value": "_TZE608_c75zqghm" }, "0x0005": { "attribute_name": "model", "value": "TS0603" } }, "unsupported_attributes": {} }, "0x0003": { "endpoint_attribute": "identify", "attributes": {}, "unsupported_attributes": {} }, "0x0004": { "endpoint_attribute": "groups", "attributes": {}, "unsupported_attributes": {} }, "0x0005": { "endpoint_attribute": "scenes", "attributes": {}, "unsupported_attributes": {} }, "0xef00": { "endpoint_attribute": null, "attributes": {}, "unsupported_attributes": {} } }, "out_clusters": { "0x000a": { "endpoint_attribute": "time", "attributes": {}, "unsupported_attributes": {} }, "0x0019": { "endpoint_attribute": "ota", "attributes": { "0x0002": { "attribute_name": "current_file_version", "value": 64 } }, "unsupported_attributes": {} } } }, "242": { "device_type": { "name": "PROXY_BASIC", "id": 97 }, "profile_id": 41440, "in_clusters": {}, "out_clusters": { "0x0021": { "endpoint_attribute": "green_power", "attributes": {}, "unsupported_attributes": {} } } } } } } ```

Logs

Logs ```python [Paste the logs here] ```

Custom quirk

Custom quirk ```python """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, Identify, 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 ( 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", ), 2: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "dp_2", ), 3: DPToAttributeMapping( ContactSwitchCluster.ep_attribute, "zone_status", converter=lambda x: IasZone.ZoneStatus.Alarm_1 if x else 0, endpoint_id=2, ), 4: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "dp_4", ), 5: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "dp_5", ), 11: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "dp_11", ), # garage door status (open, closed, ...) 12: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "dp_12", ), } 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: [ ("_TZE608_c75zqghm", "TS0603"), ], ENDPOINTS: { # 1: { PROFILE_ID: zha.PROFILE_ID, DEVICE_TYPE: zha.DeviceType.SMART_PLUG, INPUT_CLUSTERS: [ Basic.cluster_id, Identify.cluster_id, Groups.cluster_id, Scenes.cluster_id, TuyaGarageManufCluster.cluster_id, ], OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id], }, #

Additional information

I'm new to HA and Zigbee, still on the learning path, any help is welcom :)

link to the device: https://fr.aliexpress.com/item/1005007082676496.html?spm=a2g0o.order_list.order_list_main.17.2a035e5bCYzg5r&gatewayAdapt=glo2fra

G4LLY commented 1 month ago

Was able to make it work by adapting an existing quirks fro T601: Probably not the most optimal code as this is a first for me, but it work ^^

Don't hesitate to correct / advice !

"""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, Identify, 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 (
    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",
        ),
        2: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "dp_2",
        ),
        3: DPToAttributeMapping(
            ContactSwitchCluster.ep_attribute,
            "zone_status",
            converter=lambda x: IasZone.ZoneStatus.Alarm_1 if x else 0,
            endpoint_id=2,
        ),
        4: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "dp_4",
        ),
        5: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "dp_5",
        ),
        11: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "dp_11",
        ),
        # garage door status (open, closed, ...)
        12: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "dp_12",
        ),
    }

    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: [
            ("_TZE608_c75zqghm", "TS0603"),

        ],
        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,
                    Identify.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],
            },
        },
    }
gs-danielnikonczuk commented 1 week ago

I used your quirk for HA ZHA, and it worked nicely, but only for the switch to short the garage engine to send an open/stop/close signal. Unfortunately, it didn't work for the sensor. I'm not sure if I have a broken device or need additional adaptation. My devices is similar, but somehow a little bit different. Different Manufacturer, for sure. Device: TS0603 Man: _TZE608_fmemczv1

G4LLY commented 1 week ago

Hi,

for me it appear with 2 entities for the devices, The fact it is presented as a "smart plug" andnot a door opener, the smart plug status is not linked to the sensor status, to work around this I have set 2 cards in the frontend and hide the smart plug status:

Lovelace cards: 2024-08-29 13_50_04-Aperçu – Home Assistant — Mozilla Firefox

ZHA device image

gs-danielnikonczuk commented 1 week ago

I see two entities, but the state doesn't change whether I close the sensor. That's why I wonder whether I have a broken device or installed it the wrong way. HA Local 2024-08-29 at 2 02 33 PM

G4LLY commented 1 week ago

Works for me, the door sensor is properly plug on the S1 and S2 plugs ?

image

gs-danielnikonczuk commented 1 week ago

Yep. When I put the magnetic sensor parts together, nothing happens. I have other similar sensors, and they work very snappy, like in an instant. FCE0819E-6B7B-4DE5-BC97-89403BBEAC3D_1_201_a-kopia F6C20E6C-4542-4E77-981B-4691E55CCE66_1_102_a Atrakcyjne ceny 2024-08-29 at 2 23 52 PM

G4LLY commented 1 week ago

seems to be S3 and S4 at your side.

I remember I had to restart HA couple of time to make it work.

gs-danielnikonczuk commented 1 week ago

You wouldn't believe how many restarts I'm after. I wonder if this is a matter of broken sensor/device or just the converter/quirk matter in ZHA. I'll try to short the sensor entry (by a simple wire) and see if the state changes.

EDIT: Okay, I confirm that my sensor is busted. I shorted the S3 and S4, which popped up properly in the HA entity view. It could just come broken already. 💩 happens 😂

Sorry for bothering you. Good job with the quirk! It is sufficient for all the automations I wanted with the garage gate.

G4LLY commented 1 week ago

Happy you figured it out !

Cheers

jlmadrugac commented 3 days ago

Hi all. I've just added your quirk and working (only changing signature).

The problem is the presentation of the icon... it apears as a on/off icon and I'd love have it as my other garage door opener (wifi) that has an arrow to open or close (see pics)

ZB TS0603 with G4LLY quirk image

Loratap Tuya wifi connected garage opener: image

Any clues of how to represent this as the second one?