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
684 stars 637 forks source link

[Device Support Request] TS0021 _TZ3210_3ulg9kpo Tuya 2 gang switch #2847

Open BenRoe opened 6 months ago

BenRoe commented 6 months ago

Problem description

Tuya TS0021 is found in ZHA, but offers no functionality. Shows a switch control that does nothing.

Solution description

Add two way switch function.

Screenshots/Video

Screenshots/Video Bildschirmfoto 2023-12-19 um 10 44 14 Bildschirmfoto 2023-12-19 um 10 47 56

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=True, *is_full_function_device=False, *is_mains_powered=False, *is_receiver_on_when_idle=False, *is_router=False, *is_security_capable=False)", "endpoints": { "1": { "profile_id": "0x0104", "device_type": "0x0402", "input_clusters": [ "0x0000", "0x0001", "0x0500", "0xef00" ], "output_clusters": [ "0x000a", "0x0019" ] } }, "manufacturer": "_TZ3210_3ulg9kpo", "model": "TS0021", "class": "zigpy.device.Device" } ```

Diagnostic information

Diagnostic information ```json { "home_assistant": { "installation_type": "Home Assistant Container", "version": "2023.12.3", "dev": false, "hassio": false, "virtualenv": false, "python_version": "3.11.6", "docker": true, "arch": "aarch64", "timezone": "Europe/Berlin", "os_name": "Linux", "os_version": "6.1.21-v8+", "run_as_root": true }, "custom_components": { "powercalc": { "version": "v1.9.10", "requirements": [ "numpy>=1.21.1" ] }, "hacs": { "version": "1.33.0", "requirements": [ "aiogithubapi>=22.10.1" ] }, "adaptive_lighting": { "version": "1.20.0", "requirements": [ "ulid-transform" ] }, "thermal_comfort": { "version": "2.2.0", "requirements": [] }, "ics_calendar": { "version": "4.1.0", "requirements": [ "ics>=0.7.2", "recurring_ical_events>=2.0.2", "icalendar>=5.0.4" ] }, "homematicip_local": { "version": "1.50.0", "requirements": [ "hahomematic==2023.12.1" ] } }, "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.37.3", "pyserial==3.5", "pyserial-asyncio==0.6", "zha-quirks==0.0.107", "zigpy-deconz==0.22.2", "zigpy==0.60.1", "zigpy-xbee==0.20.1", "zigpy-zigate==0.12.0", "zigpy-znp==0.12.0", "universal-silabs-flasher==0.0.15", "pyserial-asyncio-fast==0.11" ], "usb": [ { "vid": "10C4", "pid": "EA60", "description": "*2652*", "known_devices": [ "slae.sh cc2652rb stick" ] }, { "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*" } ], "is_built_in": true }, "data": { "ieee": "**REDACTED**", "nwk": 33192, "manufacturer": "_TZ3210_3ulg9kpo", "model": "TS0021", "name": "_TZ3210_3ulg9kpo TS0021", "quirk_applied": false, "quirk_class": "zigpy.device.Device", "quirk_id": null, "manufacturer_code": 4417, "power_source": "Battery or Unknown", "lqi": 216, "rssi": -46, "last_seen": "2023-12-19T09:30:33", "available": true, "device_type": "EndDevice", "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=True, *is_full_function_device=False, *is_mains_powered=False, *is_receiver_on_when_idle=False, *is_router=False, *is_security_capable=False)", "endpoints": { "1": { "profile_id": "0x0104", "device_type": "0x0402", "input_clusters": [ "0x0000", "0x0001", "0x0500", "0xef00" ], "output_clusters": [ "0x000a", "0x0019" ] } }, "manufacturer": "_TZ3210_3ulg9kpo", "model": "TS0021" }, "active_coordinator": false, "entities": [ { "entity_id": "binary_sensor.tz3210_3ulg9kpo_ts0021_offnet", "name": "_TZ3210_3ulg9kpo TS0021" }, { "entity_id": "sensor.tz3210_3ulg9kpo_ts0021_batterie", "name": "_TZ3210_3ulg9kpo TS0021" } ], "neighbors": [], "routes": [], "endpoint_names": [ { "name": "IAS_ZONE" } ], "user_given_name": null, "device_reg_id": "9028a96b2f18352d641caee8963e8747", "area_id": "wohnzimmer", "cluster_details": { "1": { "device_type": { "name": "IAS_ZONE", "id": 1026 }, "profile_id": 260, "in_clusters": { "0x0001": { "endpoint_attribute": "power", "attributes": { "0x0021": { "attribute_name": "battery_percentage_remaining", "value": 0 }, "0x0020": { "attribute_name": "battery_voltage", "value": 0 } }, "unsupported_attributes": { "0x0031": { "attribute_name": "battery_size" }, "0x0033": { "attribute_name": "battery_quantity" } } }, "0x0500": { "endpoint_attribute": "ias_zone", "attributes": { "0x0010": { "attribute_name": "cie_addr", "value": "bc:02:6e:ff:fe:56:58:de" }, "0x0000": { "attribute_name": "zone_state", "value": 0 }, "0x0002": { "attribute_name": "zone_status", "value": 0 }, "0x0001": { "attribute_name": "zone_type", "value": 21 } }, "unsupported_attributes": {} }, "0xef00": { "endpoint_attribute": null, "attributes": {}, "unsupported_attributes": {} }, "0x0000": { "endpoint_attribute": "basic", "attributes": { "0x0004": { "attribute_name": "manufacturer", "value": "_TZ3210_3ulg9kpo" }, "0x0005": { "attribute_name": "model", "value": "TS0021" } }, "unsupported_attributes": {} } }, "out_clusters": { "0x0019": { "endpoint_attribute": "ota", "attributes": {}, "unsupported_attributes": {} }, "0x000a": { "endpoint_attribute": "time", "attributes": {}, "unsupported_attributes": {} } } } } } } ```

Logs

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

Custom quirk

Custom quirk ```python [Paste your custom quirk here] ```

Additional information

At zigbee2mqtt is already a open PR for that device. https://github.com/Koenkk/zigbee2mqtt/discussions/19765

The device can be found here https://www.aliexpress.com/item/1005006126584104.html

accik commented 6 months ago

image I also have TS0021 but it's showing as "smoke". I can provide more info if needed, hope to see this included in ZHA.

CoarsePiglet commented 6 months ago

I also have this device but for me it shows Opening as the Sensor 2024 01 02-21 19 43

jaimezapa commented 5 months ago

Hi, I had the same issue here. Didn't manage to write a quirk but instead wrote an automation that triggers when the error shows up in the logs. Works perfectly fine. From the "unknow cluster command " message you can determine both the button and the type of tap. See automation below. Hope it helps ;)

alias: Boton setup jaime description: "" trigger:

hexalm commented 4 months ago

Well I've been making my first foray into zigpy for these switches. I've got the signature piece down, but could probably do with little hint on what I've got wrong below. Presumably it's just the clusters I'm mapping don't have the needed functionality. I've tried a number of combinations without full success on using button presses to do anything.

Currently the devce is recognized, but looks like I have not properly assigned datapoint handlers (it's clear the TuyaNewManufCluster is falling through all of the things it can handle). I'm a bit hazy on how to determine if an existing class works or what. (The zigbee2mqtt implementation didn't look complicated, but I stll haven't grasped how it works enough to translate to zigpy.)

Including log results when I press the buttons now (I could have sworn dp 10 for batteries hadn't done this before when I understood less of what I was doing, but oh well). It pretty a straightforward missing implementation, though

2024-02-11 20:23:20.264 DEBUG (MainThread) [zigpy.zcl] [0x3721:1:0xef00] No datapoint handler for TuyaDatapointData(dp=2, data=TuyaData(dp_type=<TuyaDPType.ENUM: 4>, function=0, raw=b'\x00', *payload=<enum8.undefined_0x00: 0>))
2024-02-11 20:29:15.481 DEBUG (MainThread) [zigpy.zcl] [0x3721:1:0xef00] No datapoint handler for TuyaDatapointData(dp=1, data=TuyaData(dp_type=<TuyaDPType.ENUM: 4>, function=0, raw=b'\x00', *payload=<enum8.undefined_0x00: 0>))
2024-02-11 21:19:40.839 DEBUG (MainThread) [zigpy.zcl] [0x3721:1:0xef00] No datapoint handler for TuyaDatapointData(dp=10, data=TuyaData(dp_type=<TuyaDPType.VALUE: 2>, function=0, raw=b'\x00\x00\x00\x00', *payload=0))

My quirk:

"""Tuya 2 Button Remote."""

from zigpy.profiles import zha
from zigpy.quirks import CustomDevice
from zigpy.zcl.clusters.general import Basic, OnOff, Ota, PowerConfiguration, Time
from zigpy.zcl.clusters.security import IasZone

from zhaquirks.const import (
    BUTTON_1,
    BUTTON_2,
    COMMAND,
    DEVICE_TYPE,
    DOUBLE_PRESS,
    ENDPOINT_ID,
    ENDPOINTS,
    INPUT_CLUSTERS,
    LONG_PRESS,
    MODEL,
    OUTPUT_CLUSTERS,
    PROFILE_ID,
    SHORT_PRESS,
)
from zhaquirks.tuya import TuyaNewManufCluster, TuyaNoBindPowerConfigurationCluster, TuyaSmartRemoteOnOffCluster, TuyaSwitch

class TuyaSmartRemote0021(TuyaSwitch):
    """Tuya 2-button remote device."""

    signature = {
        MODEL: "TS0021",
        ENDPOINTS: {
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.IAS_ZONE,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    PowerConfiguration.cluster_id,
                    IasZone.cluster_id,
                    TuyaNewManufCluster.cluster_id,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            2: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.IAS_ZONE,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    PowerConfiguration.cluster_id,
                    IasZone.cluster_id,
                    TuyaNewManufCluster.cluster_id,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
        },
    }
    replacement = {
        ENDPOINTS: {
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.REMOTE_CONTROL,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    TuyaNoBindPowerConfigurationCluster,
                    TuyaSmartRemoteOnOffCluster,
                    TuyaNewManufCluster,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            2: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.REMOTE_CONTROL,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    TuyaNoBindPowerConfigurationCluster,
                    TuyaSmartRemoteOnOffCluster,
                    TuyaNewManufCluster,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
        },
    }

    device_automation_triggers = {
        (SHORT_PRESS, BUTTON_1): {ENDPOINT_ID: 1, COMMAND: SHORT_PRESS},
        (LONG_PRESS, BUTTON_1): {ENDPOINT_ID: 1, COMMAND: LONG_PRESS},
        (DOUBLE_PRESS, BUTTON_1): {ENDPOINT_ID: 1, COMMAND: DOUBLE_PRESS},
        (SHORT_PRESS, BUTTON_2): {ENDPOINT_ID: 2, COMMAND: SHORT_PRESS},
        (LONG_PRESS, BUTTON_2): {ENDPOINT_ID: 2, COMMAND: LONG_PRESS},
        (DOUBLE_PRESS, BUTTON_2): {ENDPOINT_ID: 2, COMMAND: DOUBLE_PRESS},
    }

I'm moderately experienced with python and handy with grep, so I can easily troubleshoot more or provide more info, but I'm hoping someone witth more knowledge of this software can easily spot the issue!

CoarsePiglet commented 4 months ago

@jaimezapa Do you mind posting your reply as code block because I cannot get it to work. Thanks

jaimezapa commented 4 months ago

@jaimezapa Do you mind posting your reply as code block because I cannot get it to work. Thanks

Here you have it. Bare in mind that you need to add this to your config file system_log: fire_event: true

Then check the logs to get the device ID

alias: Boton setup jaime
description: ""
trigger:
  - platform: event
    event_type: system_log_event
    event_data:
      name: zigpy.zcl
    id: liso
condition:
  - condition: template
    value_template: "{{ \"0xCECD:1:0xef00\" in trigger.event.data.message[0] }}"
    enabled: true
action:
  - choose:
      - conditions:
          - condition: template
            value_template: "{{ \"\\\\x02\\\\x04\" in trigger.event.data.message[0] }}"
        sequence:
          - choose:
              - conditions:
                  - condition: template
                    value_template: "{{ \"\\\\x01\\\\x00\" in trigger.event.data.message[0] }}"
                sequence:
                  - service: light.toggle
                    target:
                      entity_id: light.suite_all_studio_all
                    data: {}
                alias: single
              - conditions:
                  - condition: template
                    value_template: "{{ \"\\\\x01\\\\x01\" in trigger.event.data.message[0] }}"
                sequence:
                  - service: switch.toggle
                    target:
                      entity_id: switch.adaptive_lighting_sleep_mode_al_studio
                    data: {}
                alias: double
              - conditions:
                  - condition: template
                    value_template: "{{ \"\\\\x01\\\\x02\" in trigger.event.data.message[0] }}"
                sequence: []
                alias: long
        alias: boton liso
      - conditions:
          - condition: template
            value_template: "{{ \"\\\\x01\\\\x04\" in trigger.event.data.message[0] }}"
        sequence:
          - choose:
              - conditions:
                  - condition: template
                    value_template: "{{ \"\\\\x01\\\\x00\" in trigger.event.data.message[0] }}"
                sequence:
                  - if:
                      - condition: device
                        type: is_off
                        device_id: 1220c3eafc6d7f8b55d55f95e5ebbfc4
                        entity_id: 50111a494b1a6048f7a9cbd971090e28
                        domain: light
                    then:
                      - service: light.turn_on
                        target:
                          device_id: []
                          area_id: []
                          entity_id: light.ewelink_ck_bl702_al_01_7009_z102lg03_1_light_8
                        data: {}
                      - service: light.turn_on
                        target:
                          device_id: []
                          area_id: []
                          entity_id: light.ewelink_ck_bl702_al_01_7009_z102lg03_1_light_8
                        data:
                          rgb_color:
                            - 255
                            - 56
                            - 0
                      - service: adaptive_lighting.set_manual_control
                        data:
                          manual_control: true
                          entity_id: switch.adaptive_lighting_adaptative_lights_1
                    else:
                      - service: adaptive_lighting.set_manual_control
                        data:
                          manual_control: false
                          entity_id: switch.adaptive_lighting_adaptative_lights_1
                      - delay:
                          hours: 0
                          minutes: 0
                          seconds: 5
                          milliseconds: 0
                      - service: light.turn_off
                        target:
                          device_id: []
                          area_id: []
                          entity_id: light.all_lights_hall_lights
                        data: {}
                alias: single
              - conditions:
                  - condition: template
                    value_template: "{{ \"\\\\x01\\\\x01\" in trigger.event.data.message[0] }}"
                sequence:
                  - service: media_player.media_next_track
                    target:
                      entity_id: media_player.spotify_jaime
                    data: {}
                alias: double
              - conditions:
                  - condition: template
                    value_template: "{{ \"\\\\x01\\\\x02\" in trigger.event.data.message[0] }}"
                sequence:
                  - service: switch.toggle
                    target:
                      entity_id: switch.setup_jaime_switch
                    data: {}
                alias: long
        alias: boton rugoso
    default: []
  - delay:
      hours: 0
      minutes: 0
      seconds: 3
      milliseconds: 0
    enabled: true
mode: single
CoarsePiglet commented 4 months ago

@jaimezapa I have just tried an even simpler version and it doesn't work. I managed to troubleshoot it and the automation is not triggering because ZHA is firing "DEBUG" events and the system_log_event type only accepts ERROR and WARNING...

When I manually create a notification using system_log.write, with default level, it does trigger the automation. But not when using DEBUG.

Is ZHA sending ERROR/WARNING level into your log on your end? If so, how did you manage it to do that?

jaimezapa commented 4 months ago

I see. Yesterday I upgraded HA to 2024.2 and looks like they've changed the log type. In the 2023.10 version zigpy would throw this as a warning instead of debug. Guess we'll have to come up with a workaround...

[zigpy.zcl] [0xB9C5:1:0xef00] No explicit handler for cluster command 0x06: b'\x02r\x02\x04\x00\x01\x00'

jaimezapa commented 4 months ago

Maybe monitor the stdout logs from outside the container and throw MQTT messages?

CoarsePiglet commented 4 months ago

To be honest, I'm giving up. The effort is not worth it over just buying another €10 button on AliExpress. Thank you very much for the responses here anyway @jaimezapa

jaimezapa commented 4 months ago

Feel free to try this code, I run it as a service and works like a charm. Note that debug should stay enabled for zigpy (tomorrow I´ll see if I can filter the with logger first)

''' import os import sys import time import re import subprocess

Path to the Home Assistant log file

log_file_path = "volumes/home_assistant/home-assistant.log"

Regular expression pattern_lucia to match the specified entry

pattern_lucia = re.compile(r"[0xB9C5:1:0xef00]") pattern_jaime = re.compile(r"[0xCECD:1:0xef00] Unknown cluster command")

Webhook URL for sending the curl command

webhook_url_lucia = "http://your-HA:8123/api/webhook/Setup-lucia" webhook_url_jaime = "http://your-HA:8123/api/webhook/Setup-jaime"

Function to send the curl command

def send_curl_command(code,webhook): curl_command = ["curl", "-X", "POST", "-d", f"code={code}", webhook] subprocess.run(curl_command)

Function to check for the pattern_lucia in the log file

def check_log(): with open(log_file_path, "r") as f:

Move the file pointer to the end of the file

    f.seek(0, os.SEEK_END)

    # Continuously monitor new lines added to the log file
    while True:
        line = f.readline()
        if line:
            # Check if the line contains the specified pattern_lucia
            if pattern_lucia.search(line):
                # Extract the code from the log entry
                match = re.search(r"b'([^']+)'", line)
                if match:
                    code = match.group(1)
                    # Send the curl command with the code
                    send_curl_command(code,webhook_url_lucia)

            # Check if the line contains the specified pattern_jaime
            if pattern_jaime.search(line):
                # Extract the code from the log entry
                match = re.search(r"b'([^']+)'", line)
                if match:
                    code = match.group(1)
                    # Send the curl command with the code
                    send_curl_command(code,webhook_url_jaime)
        else:
            # Sleep for a short interval before checking again
            time.sleep(0.1)

try:

Start monitoring the log file

check_log()

except KeyboardInterrupt:

Handle keyboard interrupt

sys.exit(0)

except Exception as e:

Handle other exceptions

print("Error:", e)

'''

NdK73 commented 3 months ago

I have the same switch. The first time I added it, it got recognized as smoke detector (like @accik 's one) then I removed it and added a second time and it got recognized as an opening detector (like @CoarsePiglet 's one). Removing and adding it again seems to retain the opening configuration. I also tried initiating the pairing process from both buttons and tried to repeatly press 'em during pairing. No change. :(

NdK73 commented 3 months ago

I'm trying to write a quirk based on @hexalm 's code. But it seems ZHA does not use my quirk, just its default code:

Enabling debug log, I can see:


2024-03-17 09:40:55.501 DEBUG (MainThread) [homeassistant.components.zha.core.discovery] Discovering entities for device: a4:c1:38:a1:0b:25:6a:cf-_TZ3210_3ulg9kpo TS0021
2024-03-17 09:40:55.501 DEBUG (MainThread) [homeassistant.components.zha.core.discovery] Attempting to discover quirks v2 entities for device: a4:c1:38:a1:0b:25:6a:cf-_TZ3210_3ulg9kpo TS0021
2024-03-17 09:40:55.501 DEBUG (MainThread) [homeassistant.components.zha.core.discovery] Device: a4:c1:38:a1:0b:25:6a:cf-_TZ3210_3ulg9kpo TS0021 is not a quirks v2 device - skipping discover_quirks_v2_entities
2024-03-17 09:40:55.501 DEBUG (MainThread) [homeassistant.components.zha.core.gateway] device - 0xAF62:a4:c1:38:a1:0b:25:6a:cf entering async_device_initialized - is_new_join: True
2024-03-17 09:40:55.501 DEBUG (MainThread) [homeassistant.components.zha.core.gateway] device - 0xAF62:a4:c1:38:a1:0b:25:6a:cf has joined the ZHA zigbee network
2024-03-17 09:40:55.502 DEBUG (MainThread) [homeassistant.components.zha.core.device] [0xAF62](TS0021): started configuration
2024-03-17 09:40:55.502 DEBUG (MainThread) [homeassistant.components.zha.core.cluster_handlers] [0xAF62:ZDO](TS0021): 'async_configure' stage succeeded
2024-03-17 09:40:55.505 DEBUG (MainThread) [homeassistant.components.zha.core.cluster_handlers] [0xAF62:1:0x0001]: Performing cluster binding
2024-03-17 09:40:55.507 DEBUG (MainThread) [homeassistant.components.zha.core.cluster_handlers] [0xAF62:1:0x0000]: Configuring cluster attribute reporting
2024-03-17 09:40:55.507 DEBUG (MainThread) [homeassistant.components.zha.core.cluster_handlers] [0xAF62:1:0x0000]: finished cluster handler configuration
2024-03-17 09:40:55.507 DEBUG (MainThread) [homeassistant.components.zha.core.cluster_handlers] [0xAF62:1:0xef00]: Performing cluster binding
2024-03-17 09:40:55.508 DEBUG (MainThread) [homeassistant.components.zha.core.cluster_handlers] [0xAF62:1:0x0019]: finished cluster handler configuration
2024-03-17 09:40:55.508 DEBUG (MainThread) [homeassistant.components.zha.core.cluster_handlers] [0xAF62:1:0x0019]: finished cluster handler configuration
2024-03-17 09:40:55.707 DEBUG (MainThread) [homeassistant.components.zha.core.cluster_handlers] [0xAF62:1:0x0500]: started IASZoneClusterHandler configuration
2024-03-17 09:40:55.784 DEBUG (MainThread) [homeassistant.components.zha.core.cluster_handlers] [0xAF62:1:0x0001]: bound 'power' cluster: Status.SUCCESS
2024-03-17 09:40:55.784 DEBUG (MainThread) [homeassistant.components.zha.core.cluster_handlers] [0xAF62:1:0x0001]: Configuring cluster attribute reporting
2024-03-17 09:40:56.206 DEBUG (MainThread) [homeassistant.components.zha.core.cluster_handlers] [0xAF62:1:0x0500]: bound 'ias_zone' cluster: Status.SUCCESS
2024-03-17 09:40:56.284 DEBUG (MainThread) [homeassistant.components.zha.core.cluster_handlers] [0xAF62:1:0x0001]: Failed to configure reporting for '[]' on 'power' cluster: [ConfigureReportingResponseRecord(status=<Status.SUCCESS: 0>), ConfigureReportingResponseRecord(status=<Status.SUCCESS: 0>)]
2024-03-17 09:40:56.284 DEBUG (MainThread) [homeassistant.components.zha.core.cluster_handlers] [0xAF62:1:0x0001]: Successfully configured reporting for '{'battery_percentage_remaining', 'battery_voltage'}' on 'power' cluster
2024-03-17 09:40:56.284 DEBUG (MainThread) [homeassistant.components.zha.core.cluster_handlers] [0xAF62:1:0x0001]: finished cluster handler configuration
2024-03-17 09:40:58.342 DEBUG (MainThread) [homeassistant.components.zha.core.cluster_handlers] [0xAF62:1:0x0500]: Enroll requested
2024-03-17 09:40:58.342 DEBUG (MainThread) [homeassistant.components.zha.core.cluster_handlers] [0xAF62:1:0x0500]: wrote cie_addr: 00:12:4b:00:25:90:6b:f1 to 'ias_zone' cluster
2024-03-17 09:40:58.342 DEBUG (MainThread) [homeassistant.components.zha.core.cluster_handlers] [0xAF62:1:0x0500]: Sending pro-active IAS enroll response
2024-03-17 09:40:58.343 DEBUG (MainThread) [homeassistant.components.zha.core.cluster_handlers] [0xAF62:1:0x0500]: finished IASZoneClusterHandler configuration```

Any hints? Tks.
hexalm commented 3 months ago

@NdK73 apologies - I found out a bit ago that the quirk signature was not being recognized, but haven't gotten back to working on this yet. I must have had a cached version or something when I thought it was working.

If you go to the service and pick the option to "configure zigbee device" (three dots next to the 'reconfigure' option) you can see the signature. I know I had it being recognized before I added the second endpoint (but still I never got the thing to give me any button entities). Tuya's stuff is just weird and I could stand help from someone who has worked on the ZHA handlers for them.

Maybe I'll look at this again today, but I'm planning to redo my architecture for HA and might be switching to zigbee2mqtt.

MikeInMaine commented 3 months ago

I'm moderately experienced with python and handy with grep, so I can easily troubleshoot more or provide more info, but I'm hoping someone witth more knowledge of this software can easily spot the issue!

Did you make any progress with this quirk? I wasn't able to get yours to load for the TS0021 switch without making some changes, such as removing the second endpoints

Drethak commented 3 months ago

So I somehow managed to get it so that switches were showing up in the entities instead of an IASZone sensor, however, neither responded to button presses from the actual device. Has anyone else made any progress with this? I was using a slightly modified version of the TS0042 code that already exists as a working quirk.

netizeni commented 2 months ago

If only I knew about this issue before ordering...

After adding this switch, I got the same thing as CoarsePiglet, ZHA recognized device as "Opening". The problem is that you don't know the device name until you add it to ZHA, hence you can't know it's not supported.

The strange thing is there are people in aliexpress' reviews saying it's working in HA for them, so I'm not sure can it be some different firmware version or what?

Mentioned zigbee2mqtt issue in OP is done almost two months ago, so is there any chance this one will be fixed soon?

NdK73 commented 2 months ago

Probably the users saying it's working are using it via a Tuya gateway. Too bad I've had no time yet to look at the issue :(

netizeni commented 2 months ago

It's really shame, as I wanted to get into zigbee ecosystem and use a switch instead of my phone for two devices, I ordered this switch as it has 2 gangs, waited for a month and looked forward, just to find out about this issue... now I have a coordinator without any zigbee devices.

Probably the users saying it's working are using it via a Tuya gateway.

They are saying it works with HA, so I'm not sure are they referring to zigbee2mqtt, or it's possible to add this switch over tuya integration instead of ZHA? I assume it won't work that way, as the coordinator is not tuya gateway, but a USB dongle E.

Unfortunately, my system uses HA core, which doesn't support addons, so I can't switch to zigbee2mqtt, and I guess the only way is to wait for someone to add the support. I looked at a code myself, but I never did anything similar, so I have no idea where to start. If there was a class for a similar switch, I could try changing source code a bit. Although, I wouldn't know how to test it.

As @Drethak mentioned, seems like this switch is also recognized in ZHA as TS0042, physically it looks the same.

MikeInMaine commented 2 months ago

I ended up ordering the following and it works great out of the box with zha. I forget to check the compatibility list before ordering items and one really should. This button has at least two functions for 1. click and 2. double-click and maybe a 3. long press.

https://www.aliexpress.us/item/3256805811361539.html?spm=a2g0o.order_list.order_list_main.29.3a951802SJXgMV&gatewayAdapt=glo2usa

It's really shame, as I wanted to get into zigbee ecosystem and use a switch instead of my phone for two devices, I ordered this switch as it has 2 gangs, waited for a month and looked forward, just to find out about this issue... now I have a coordinator without any zigbee devices.

netizeni commented 2 months ago

Believe it or not, the same device is in my cart, and I hesitated between that one and the one from OP. Unfortunately, I made a mistake and ordered this one just because it has 2 gangs.

MikeInMaine commented 2 months ago

lol, I did the same!

On Sun, Apr 21, 2024 at 11:37 AM netizeni @.***> wrote:

Believe it or not, the same device is in my cart, and I hesitated between that one and the one from OP. Unfortunately, I made a mistake and ordered this one just because it has 2 gangs.

— Reply to this email directly, view it on GitHub https://github.com/zigpy/zha-device-handlers/issues/2847#issuecomment-2068087535, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABAU7PAVTILEJZQU2NHFXNLY6PMKZAVCNFSM6AAAAABA24JCZWVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDANRYGA4DONJTGU . You are receiving this because you commented.Message ID: @.***>

ariboehme commented 2 months ago

I have the same issue here: I use HA with Tuya integration using a Tuya ZigBee gateway. In the first addition he appeared as Smoke, in the second as Contact. Pressing the second button shows it as Open in HA. But the rest doesn't work.

Tela_Sensor

netizeni commented 2 months ago

Hi @MattWestb, sorry for tagging, but seems like you are our only hope. I was digging around both source code and old issues, and you appeared helping a lot of people with similar issues regarding tuya switches. You can see how it looks in OP. This is the device signature:

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=True, *is_full_function_device=False, *is_mains_powered=False, *is_receiver_on_when_idle=False, *is_router=False, *is_security_capable=False)", "endpoints": { "1": { "profile_id": "0x0104", "device_type": "0x0402", "input_clusters": [ "0x0000", "0x0001", "0x0500", "0xef00" ], "output_clusters": [ "0x000a", "0x0019" ] } }, "manufacturer": "_TZ3210_3ulg9kpo", "model": "TS0021", "class": "zigpy.device.Device" } ```

It's strange it only has one endpoint, while TS0042 (also 2 gang switch) has two endpoints. Regardless, I tried editing ts0042.py as it seems to be the closest to this device, loaded it as a custom quirk (logs showing its presence) but it never changes the device "configuration" i.e. always showing "opening" sensor. Do you maybe know what seems to be the problem? Quirk will be pasted below, and I made sure simpleDescriptor has a correct value from a signature.

Custom quirk ```python """Tuya 2 Button Remote.""" from zigpy.profiles import zha from zigpy.quirks import CustomDevice from zigpy.zcl.clusters.general import Basic, OnOff, Ota, PowerConfiguration, Time from zhaquirks.const import ( BUTTON_1, BUTTON_2, COMMAND, DEVICE_TYPE, DOUBLE_PRESS, ENDPOINT_ID, ENDPOINTS, INPUT_CLUSTERS, LONG_PRESS, MODEL, OUTPUT_CLUSTERS, PROFILE_ID, SHORT_PRESS, ) from zhaquirks.tuya import TuyaSmartRemoteOnOffCluster class TuyaSmartRemote0021TI(CustomDevice): """Tuya 2-button remote device with time on in cluster.""" signature = { # SizePrefixedSimpleDescriptor(endpoint=1, profile=260, device_type=1026, device_version=1, input_clusters=[1, 1280, 61184, 0], output_clusters=[25, 10])) MODEL: "TS0021", ENDPOINTS: { 1: { PROFILE_ID: zha.PROFILE_ID, DEVICE_TYPE: zha.DeviceType.ON_OFF_SWITCH, INPUT_CLUSTERS: [ Basic.cluster_id, PowerConfiguration.cluster_id, OnOff.cluster_id, Time.cluster_id, ], OUTPUT_CLUSTERS: [Ota.cluster_id], }, }, } replacement = { ENDPOINTS: { 1: { PROFILE_ID: zha.PROFILE_ID, DEVICE_TYPE: zha.DeviceType.REMOTE_CONTROL, INPUT_CLUSTERS: [ Basic.cluster_id, PowerConfiguration.cluster_id, TuyaSmartRemoteOnOffCluster, Time.cluster_id, ], OUTPUT_CLUSTERS: [Ota.cluster_id], }, }, } device_automation_triggers = { (SHORT_PRESS, BUTTON_1): {ENDPOINT_ID: 1, COMMAND: SHORT_PRESS}, (LONG_PRESS, BUTTON_1): {ENDPOINT_ID: 1, COMMAND: LONG_PRESS}, (DOUBLE_PRESS, BUTTON_1): {ENDPOINT_ID: 1, COMMAND: DOUBLE_PRESS}, } class TuyaSmartRemote0021TO(CustomDevice): """Tuya 2-button remote device with time on out cluster.""" signature = { # SizePrefixedSimpleDescriptor(endpoint=1, profile=260, device_type=1026, device_version=1, input_clusters=[1, 1280, 61184, 0], output_clusters=[25, 10])) MODEL: "TS0021", ENDPOINTS: { 1: { PROFILE_ID: zha.PROFILE_ID, DEVICE_TYPE: zha.DeviceType.ON_OFF_SWITCH, INPUT_CLUSTERS: [ Basic.cluster_id, PowerConfiguration.cluster_id, OnOff.cluster_id, ], OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id], }, }, } replacement = { ENDPOINTS: { 1: { PROFILE_ID: zha.PROFILE_ID, DEVICE_TYPE: zha.DeviceType.REMOTE_CONTROL, INPUT_CLUSTERS: [ Basic.cluster_id, PowerConfiguration.cluster_id, TuyaSmartRemoteOnOffCluster, ], OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id], }, }, } device_automation_triggers = { (SHORT_PRESS, BUTTON_1): {ENDPOINT_ID: 1, COMMAND: SHORT_PRESS}, (LONG_PRESS, BUTTON_1): {ENDPOINT_ID: 1, COMMAND: LONG_PRESS}, (DOUBLE_PRESS, BUTTON_1): {ENDPOINT_ID: 1, COMMAND: DOUBLE_PRESS}, } ```
netizeni commented 2 months ago

I've spent way too much time on trying to make this work, so at this point, I will probably just switch to Z2M as it works there with no issues. Understandably, tuya devices are ridiculous and this is an open source project, but the lack of consistency in the source code and a clear documentation make this exceptionally hard to add a working device.

Here's the updated custom quirk, it still doesn't work, but somebody might want to try to fix it. To save some time to whoever tries, I went over different classes in the project and noticed fields of endpoints object are mapped based on SizePrefixedSimpleDescriptor values for that specific field. The logs were showing device_type mismatch in the previous quirk, so once I put IAS_ZONE, it now shows input clusters mismatch. Now comes the inconsistency I spoke about, as in multiple classes, the number 61184 in the input_clusters is mapped to multiple different values like TuyaManufCluster, TuyaNewManufCluster, TuyaManufClusterAttributes and a few more. There's no clear way in the logs to know is this mismatch in endpoints object or replacement object. I assume once someone finds the correct combination of input_clusters array, there won't be a mismatch, so the system will start using the custom quirk.

Custom quirk ```python """Tuya 2 Button Remote.""" from zigpy.profiles import zha from zigpy.quirks import CustomDevice from zigpy.zcl.clusters.general import Basic, OnOff, Ota, PowerConfiguration, Time from zigpy.zcl.clusters.security import IasZone from zhaquirks.const import ( BUTTON_1, BUTTON_2, COMMAND, DEVICE_TYPE, DOUBLE_PRESS, ENDPOINT_ID, ENDPOINTS, INPUT_CLUSTERS, LONG_PRESS, MODELS_INFO, OUTPUT_CLUSTERS, PROFILE_ID, SHORT_PRESS, ) from zhaquirks.tuya import ( TuyaManufCluster, TuyaNewManufCluster, TuyaManufClusterAttributes, TuyaNoBindPowerConfigurationCluster, TuyaSmartRemoteOnOffCluster, ) class TuyaSmartRemote0021TI(CustomDevice): """Tuya 2-button remote device with time on in cluster.""" def __init__(self, *args, **kwargs): """Init device.""" self.motion_bus = Bus() super().__init__(*args, **kwargs) signature = { # SizePrefixedSimpleDescriptor(endpoint=1, profile=260, device_type=1026, device_version=1, input_clusters=[1, 1280, 61184, 0], output_clusters=[25, 10])) MODELS_INFO: [("_TZ3210_3ulg9kpo", "TS0021")], ENDPOINTS: { 1: { PROFILE_ID: zha.PROFILE_ID, DEVICE_TYPE: zha.DeviceType.IAS_ZONE, INPUT_CLUSTERS: [ OnOff.cluster_id, IasZone.cluster_id, TuyaNewManufCluster.cluster_id, Basic.cluster_id, ], OUTPUT_CLUSTERS: [Ota.cluster_id, Time.cluster_id], }, }, } replacement = { ENDPOINTS: { 1: { PROFILE_ID: zha.PROFILE_ID, DEVICE_TYPE: zha.DeviceType.REMOTE_CONTROL, INPUT_CLUSTERS: [ Basic.cluster_id, TuyaNoBindPowerConfigurationCluster, TuyaSmartRemoteOnOffCluster, Time.cluster_id, ], OUTPUT_CLUSTERS: [Ota.cluster_id, Time.cluster_id], }, }, } device_automation_triggers = { (SHORT_PRESS, BUTTON_1): {ENDPOINT_ID: 1, COMMAND: SHORT_PRESS}, (LONG_PRESS, BUTTON_1): {ENDPOINT_ID: 1, COMMAND: LONG_PRESS}, (DOUBLE_PRESS, BUTTON_1): {ENDPOINT_ID: 1, COMMAND: DOUBLE_PRESS}, } class TuyaSmartRemote0021TO(CustomDevice): """Tuya 2-button remote device with time on out cluster.""" signature = { # SizePrefixedSimpleDescriptor(endpoint=1, profile=260, device_type=1026, device_version=1, input_clusters=[1, 1280, 61184, 0], output_clusters=[25, 10])) MODELS_INFO: [("_TZ3210_3ulg9kpo", "TS0021")], ENDPOINTS: { 1: { PROFILE_ID: zha.PROFILE_ID, DEVICE_TYPE: zha.DeviceType.IAS_ZONE, INPUT_CLUSTERS: [ OnOff.cluster_id, IasZone.cluster_id, TuyaNewManufCluster.cluster_id, Basic.cluster_id, ], OUTPUT_CLUSTERS: [Ota.cluster_id, Time.cluster_id], }, }, } replacement = { ENDPOINTS: { 1: { PROFILE_ID: zha.PROFILE_ID, DEVICE_TYPE: zha.DeviceType.REMOTE_CONTROL, INPUT_CLUSTERS: [ Basic.cluster_id, TuyaNoBindPowerConfigurationCluster, TuyaSmartRemoteOnOffCluster, Time.cluster_id, ], OUTPUT_CLUSTERS: [Ota.cluster_id, Time.cluster_id], }, }, } device_automation_triggers = { (SHORT_PRESS, BUTTON_1): {ENDPOINT_ID: 1, COMMAND: SHORT_PRESS}, (LONG_PRESS, BUTTON_1): {ENDPOINT_ID: 1, COMMAND: LONG_PRESS}, (DOUBLE_PRESS, BUTTON_1): {ENDPOINT_ID: 1, COMMAND: DOUBLE_PRESS}, } ```
dtaylor commented 2 months ago

I bought this same switch and went down the rabbit hole as well. The signature part was pretty easy to get right, but I didn't get nearly as close as @netizeni did.

netizeni commented 2 months ago

This really bothers me, so I spend additional time to try to make it work. I've managed to add a correct signature combination, so now the device will show a switch toggle, and even tho it doesn't work, at least it's not "Opening" sensor anymore. Also, "Zigbee info" will now show that custom quirk is being used and under automation, you can see "first button pressed", double pressed and long pressed.

Unfortunately, single, double and long press don't work, as it seems the device doesn't fire zha_event. I will share the custom quirk below, so hopefully someone will join in fixing it. It would be also helpful if you guys, who have this device, try different combination of clusters (from different switch classes of this project) inside INPUT_CLUSTERS and OUTPUT_CLUSTERS arrays of replacement objects and report back if you find something works. My assumption is once replacement objects gets filled with correct clusters, the device will start firing events and will work as it should.

Custom quirk ```python """Tuya 2 Button Remote.""" from zigpy.profiles import zha from zigpy.quirks import CustomDevice from zigpy.zcl.clusters.general import Basic, OnOff, Ota, PowerConfiguration, Time from zigpy.zcl.clusters.security import IasZone from zhaquirks.const import ( BUTTON_1, BUTTON_2, COMMAND, DEVICE_TYPE, DOUBLE_PRESS, ENDPOINT_ID, ENDPOINTS, INPUT_CLUSTERS, LONG_PRESS, MODELS_INFO, OUTPUT_CLUSTERS, PROFILE_ID, SHORT_PRESS, ) from zhaquirks.tuya import ( TuyaManufCluster, TuyaNewManufCluster, TuyaManufClusterAttributes, TuyaNoBindPowerConfigurationCluster, TuyaSmartRemoteOnOffCluster, ) from zhaquirks.tuya.mcu import ( MoesSwitchManufCluster, TuyaOnOff, TuyaOnOffManufCluster, TuyaOnOffNM, ) class TuyaSmartRemote0021TI(CustomDevice): """Tuya 2-button remote device with time on in cluster.""" def __init__(self, *args, **kwargs): """Init device.""" self.motion_bus = Bus() super().__init__(*args, **kwargs) signature = { # SizePrefixedSimpleDescriptor(endpoint=1, profile=260, device_type=1026, device_version=1, input_clusters=[1, 1280, 61184, 0], output_clusters=[25, 10])) MODELS_INFO: [("_TZ3210_3ulg9kpo", "TS0021")], ENDPOINTS: { 1: { PROFILE_ID: zha.PROFILE_ID, DEVICE_TYPE: zha.DeviceType.IAS_ZONE, INPUT_CLUSTERS: [ PowerConfiguration.cluster_id, IasZone.cluster_id, TuyaOnOffManufCluster.cluster_id, Basic.cluster_id, ], OUTPUT_CLUSTERS: [Ota.cluster_id, Time.cluster_id], }, }, } replacement = { ENDPOINTS: { 1: { PROFILE_ID: zha.PROFILE_ID, DEVICE_TYPE: zha.DeviceType.REMOTE_CONTROL, INPUT_CLUSTERS: [ Basic.cluster_id, TuyaNoBindPowerConfigurationCluster, TuyaSmartRemoteOnOffCluster, ], OUTPUT_CLUSTERS: [Ota.cluster_id, Time.cluster_id], }, }, } device_automation_triggers = { (SHORT_PRESS, BUTTON_1): {ENDPOINT_ID: 1, COMMAND: SHORT_PRESS}, (LONG_PRESS, BUTTON_1): {ENDPOINT_ID: 1, COMMAND: LONG_PRESS}, (DOUBLE_PRESS, BUTTON_1): {ENDPOINT_ID: 1, COMMAND: DOUBLE_PRESS}, } class TuyaSmartRemote0021TO(CustomDevice): """Tuya 2-button remote device with time on out cluster.""" signature = { # SizePrefixedSimpleDescriptor(endpoint=1, profile=260, device_type=1026, device_version=1, input_clusters=[1, 1280, 61184, 0], output_clusters=[25, 10])) MODELS_INFO: [("_TZ3210_3ulg9kpo", "TS0021")], ENDPOINTS: { 1: { PROFILE_ID: zha.PROFILE_ID, DEVICE_TYPE: zha.DeviceType.IAS_ZONE, INPUT_CLUSTERS: [ PowerConfiguration.cluster_id, IasZone.cluster_id, TuyaOnOffManufCluster.cluster_id, Basic.cluster_id, ], OUTPUT_CLUSTERS: [Ota.cluster_id, Time.cluster_id], }, }, } replacement = { ENDPOINTS: { 1: { PROFILE_ID: zha.PROFILE_ID, DEVICE_TYPE: zha.DeviceType.REMOTE_CONTROL, INPUT_CLUSTERS: [ Basic.cluster_id, TuyaNoBindPowerConfigurationCluster, TuyaSmartRemoteOnOffCluster, ], OUTPUT_CLUSTERS: [Ota.cluster_id, Time.cluster_id], }, }, } device_automation_triggers = { (SHORT_PRESS, BUTTON_1): {ENDPOINT_ID: 1, COMMAND: SHORT_PRESS}, (LONG_PRESS, BUTTON_1): {ENDPOINT_ID: 1, COMMAND: LONG_PRESS}, (DOUBLE_PRESS, BUTTON_1): {ENDPOINT_ID: 1, COMMAND: DOUBLE_PRESS}, } ```
MikeInMaine commented 2 months ago

I bought a button that works OOtB from aliexpress for something like $5 delivered to the USA. At my hourly rate, that's about 1/10th of an hour. I threw this one away. I use the zigbee compatibility list before buying anything these days.

On Mon, May 6, 2024 at 5:36 PM netizeni @.***> wrote:

This really bothers me, so I spend additional time to try to make it work. I've managed to add a correct signature combination, so now the device will show a switch toggle, and even tho it doesn't work, at least it's not "Opening" sensor anymore. Also, "Zigbee info" will now show that custom quirk is being used and under automation, you can see "first button pressed", double pressed and long pressed.

Unfortunately, single, double and long press don't work, as it seems the device doesn't fire zha_event. I will share the custom quirk below, so hopefully someone will join in fixing it. It would be also helpful if you guys, who have this device, try different combination of clusters (from different switch classes of this project) inside INPUT_CLUSTERS and OUTPUT_CLUSTERS arrays of replacement objects and report back if you find something works. My assumption is once replacement objects gets filled with correct clusters, the device will start firing events and will work as it should. Custom quirk

"""Tuya 2 Button Remote.""" from zigpy.profiles import zhafrom zigpy.quirks import CustomDevicefrom zigpy.zcl.clusters.general import Basic, OnOff, Ota, PowerConfiguration, Timefrom zigpy.zcl.clusters.security import IasZone from zhaquirks.const import ( BUTTON_1, BUTTON_2, COMMAND, DEVICE_TYPE, DOUBLE_PRESS, ENDPOINT_ID, ENDPOINTS, INPUT_CLUSTERS, LONG_PRESS, MODELS_INFO, OUTPUT_CLUSTERS, PROFILE_ID, SHORT_PRESS, )from zhaquirks.tuya import ( TuyaManufCluster, TuyaNewManufCluster, TuyaManufClusterAttributes, TuyaNoBindPowerConfigurationCluster, TuyaSmartRemoteOnOffCluster, )from zhaquirks.tuya.mcu import ( MoesSwitchManufCluster, TuyaOnOff, TuyaOnOffManufCluster, TuyaOnOffNM, )

class TuyaSmartRemote0021TI(CustomDevice): """Tuya 2-button remote device with time on in cluster."""

def __init__(self, *args, **kwargs):
    """Init device."""
    self.motion_bus = Bus()
    super().__init__(*args, **kwargs)

signature = {
    # SizePrefixedSimpleDescriptor(endpoint=1, profile=260, device_type=1026, device_version=1, input_clusters=[1, 1280, 61184, 0], output_clusters=[25, 10]))
    MODELS_INFO: [("_TZ3210_3ulg9kpo", "TS0021")],
    ENDPOINTS: {
        1: {
            PROFILE_ID: zha.PROFILE_ID,
            DEVICE_TYPE: zha.DeviceType.IAS_ZONE,
            INPUT_CLUSTERS: [
                PowerConfiguration.cluster_id,
                IasZone.cluster_id,
                TuyaOnOffManufCluster.cluster_id,
                Basic.cluster_id,
            ],
            OUTPUT_CLUSTERS: [Ota.cluster_id, Time.cluster_id],
        },
    },
}
replacement = {
    ENDPOINTS: {
        1: {
            PROFILE_ID: zha.PROFILE_ID,
            DEVICE_TYPE: zha.DeviceType.REMOTE_CONTROL,
            INPUT_CLUSTERS: [
                Basic.cluster_id,
                TuyaNoBindPowerConfigurationCluster,
                TuyaSmartRemoteOnOffCluster,
            ],
            OUTPUT_CLUSTERS: [Ota.cluster_id, Time.cluster_id],
        },
    },
}

device_automation_triggers = {
    (SHORT_PRESS, BUTTON_1): {ENDPOINT_ID: 1, COMMAND: SHORT_PRESS},
    (LONG_PRESS, BUTTON_1): {ENDPOINT_ID: 1, COMMAND: LONG_PRESS},
    (DOUBLE_PRESS, BUTTON_1): {ENDPOINT_ID: 1, COMMAND: DOUBLE_PRESS},
}

class TuyaSmartRemote0021TO(CustomDevice): """Tuya 2-button remote device with time on out cluster."""

signature = {
    # SizePrefixedSimpleDescriptor(endpoint=1, profile=260, device_type=1026, device_version=1, input_clusters=[1, 1280, 61184, 0], output_clusters=[25, 10]))
    MODELS_INFO: [("_TZ3210_3ulg9kpo", "TS0021")],
    ENDPOINTS: {
        1: {
            PROFILE_ID: zha.PROFILE_ID,
            DEVICE_TYPE: zha.DeviceType.IAS_ZONE,
            INPUT_CLUSTERS: [
                PowerConfiguration.cluster_id,
                IasZone.cluster_id,
                TuyaOnOffManufCluster.cluster_id,
                Basic.cluster_id,
            ],
            OUTPUT_CLUSTERS: [Ota.cluster_id, Time.cluster_id],
        },
    },
}
replacement = {
    ENDPOINTS: {
        1: {
            PROFILE_ID: zha.PROFILE_ID,
            DEVICE_TYPE: zha.DeviceType.REMOTE_CONTROL,
            INPUT_CLUSTERS: [
                Basic.cluster_id,
                TuyaNoBindPowerConfigurationCluster,
                TuyaSmartRemoteOnOffCluster,
            ],
            OUTPUT_CLUSTERS: [Ota.cluster_id, Time.cluster_id],
        },
    },
}

device_automation_triggers = {
    (SHORT_PRESS, BUTTON_1): {ENDPOINT_ID: 1, COMMAND: SHORT_PRESS},
    (LONG_PRESS, BUTTON_1): {ENDPOINT_ID: 1, COMMAND: LONG_PRESS},
    (DOUBLE_PRESS, BUTTON_1): {ENDPOINT_ID: 1, COMMAND: DOUBLE_PRESS},
}

— Reply to this email directly, view it on GitHub https://github.com/zigpy/zha-device-handlers/issues/2847#issuecomment-2096965516, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABAU7PAF2SSU4ESSHMWB3S3ZA7ZXLAVCNFSM6AAAAABA24JCZWVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDAOJWHE3DKNJRGY . You are receiving this because you commented.Message ID: @.***>

netizeni commented 2 months ago

I understand your point of view, Mike, it's valid, but at least in my case, this is not about the price itself. It's about helping the community and all the people who have or will have this switch. If there weren't all these people selflessly contributing before us, we wouldn't be able to use zha integration.

All in all, I hope someone will join in to help and make this work. I assume Matt West would be able to solve this easily, but I don't want to tag him again.

MikeInMaine commented 2 months ago

I definitely agree. On the other hand, manufacturers who build nonstandard stuff shouldn't be supported.

On Tue, May 7, 2024 at 7:20 AM netizeni @.***> wrote:

I understand your point of view, Mike, it's valid, but at least in my case, this is not about the price itself. It's about helping the community and all the people who have or will have this switch. If there weren't all these people selflessly contributing before us, we wouldn't be able to use zha integration.

All in all, I hope someone will join in to help and make this work. I assume Matt West would be able to solve this easily, but I don't want to tag him again.

— Reply to this email directly, view it on GitHub https://github.com/zigpy/zha-device-handlers/issues/2847#issuecomment-2098170606, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABAU7PAP5SIWU4GZCON2UMDZBC2HTAVCNFSM6AAAAABA24JCZWVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDAOJYGE3TANRQGY . You are receiving this because you commented.Message ID: @.***>

rbaron commented 4 days ago

I finally came up with a hacky quirk that seems to work okay on my system (core 2024.7.0):

"""Device handler the TS0021."""
from zhaquirks import EventableCluster
from zigpy.quirks import CustomDevice
from zhaquirks.const import (
    ARGS,
    ATTRIBUTE_ID,
    CLUSTER_ID,
    COMMAND_ATTRIBUTE_UPDATED,
    COMMAND,
    SKIP_CONFIGURATION,
    DEVICE_TYPE,
    ENDPOINT_ID,
    ENDPOINTS,
    INPUT_CLUSTERS,
    LONG_PRESS,
    MODELS_INFO,
    OUTPUT_CLUSTERS,
    PARAMS,
    PROFILE_ID,
    SHORT_PRESS,
    DOUBLE_PRESS,
    VALUE,
)
from zhaquirks.tuya import (
    TUYA_CLUSTER_ID,
    TuyaNewManufCluster,
    DPToAttributeMapping,
)

BTN_1 = "Button 1"
BTN_2 = "Button 2"

COMMAND_1_PRESSED = 'btn_1_pressed'
COMMAND_2_PRESSED = 'btn_2_pressed'

class TuyaCustomCluster(TuyaNewManufCluster, EventableCluster):
    dp_to_attribute = {
        1: DPToAttributeMapping(
            TuyaNewManufCluster.ep_attribute,
            COMMAND_1_PRESSED,
        ),
        2: DPToAttributeMapping(
            TuyaNewManufCluster.ep_attribute,
            COMMAND_2_PRESSED,
        ),
    }

    data_point_handlers = {
        1: "_dp_2_attr_update",
        2: "_dp_2_attr_update",
    }

class TS0021(CustomDevice):
    signature = {
        MODELS_INFO: [("_TZ3210_3ulg9kpo", "TS0021")],
        ENDPOINTS: {
            1: {
                PROFILE_ID: 0x0104,
                DEVICE_TYPE: 0x0402,
                INPUT_CLUSTERS: [
                    0x0000,
                    0x0001,
                    0x0500,
                    0xef00,
                ],
                OUTPUT_CLUSTERS: [
                    0x000a,
                    0x0019,
                ],
            },
        },
    }

    replacement = {
        SKIP_CONFIGURATION: False,
        ENDPOINTS: {
            1: {
                PROFILE_ID: 0x0104,
                DEVICE_TYPE: 0x0402,
                INPUT_CLUSTERS: [
                    0x0000,
                    0x0001,
                    0x0500,
                    TuyaCustomCluster,
                ],
                OUTPUT_CLUSTERS: [
                    0x000a,
                    0x0019,
                ],
            },
        },
    }

    device_automation_triggers = {
        (SHORT_PRESS, BTN_1): {
            COMMAND: COMMAND_ATTRIBUTE_UPDATED,
            CLUSTER_ID: TUYA_CLUSTER_ID,
            ENDPOINT_ID: 1,
            ARGS: {ATTRIBUTE_ID: COMMAND_1_PRESSED, VALUE: 0},
        },
        (DOUBLE_PRESS, BTN_1): {
            COMMAND: COMMAND_ATTRIBUTE_UPDATED,
            CLUSTER_ID: TUYA_CLUSTER_ID,
            ENDPOINT_ID: 1,
            ARGS: {ATTRIBUTE_ID: COMMAND_1_PRESSED, VALUE: 1},
        },
        (LONG_PRESS, BTN_1): {
            COMMAND: COMMAND_ATTRIBUTE_UPDATED,
            CLUSTER_ID: TUYA_CLUSTER_ID,
            ENDPOINT_ID: 1,
            ARGS: {ATTRIBUTE_ID: COMMAND_1_PRESSED, VALUE: 2},
        },
        (SHORT_PRESS, BTN_2): {
            COMMAND: COMMAND_ATTRIBUTE_UPDATED,
            CLUSTER_ID: TUYA_CLUSTER_ID,
            ENDPOINT_ID: 1,
            ARGS: {ATTRIBUTE_ID: COMMAND_2_PRESSED, VALUE: 0},
        },
        (DOUBLE_PRESS, BTN_2): {
            COMMAND: COMMAND_ATTRIBUTE_UPDATED,
            CLUSTER_ID: TUYA_CLUSTER_ID,
            ENDPOINT_ID: 1,
            ARGS: {ATTRIBUTE_ID: COMMAND_2_PRESSED, VALUE: 1},
        },
        (LONG_PRESS, BTN_2): {
            COMMAND: COMMAND_ATTRIBUTE_UPDATED,
            CLUSTER_ID: TUYA_CLUSTER_ID,
            ENDPOINT_ID: 1,
            ARGS: {ATTRIBUTE_ID: COMMAND_2_PRESSED, VALUE: 2},
        },
    }