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
737 stars 675 forks source link

[Device Support Request] For Tuya curtain motor #3081

Open hami89 opened 6 months ago

hami89 commented 6 months ago

Problem description

My Tuya curtain motor is not recognized by HA. "This device is currently sold as "Tuya curtain motor gen 3". Support for the same device has already been added to zigbee2mqtt, maybe this provides some additional info about the device. https://github.com/Koenkk/zigbee2mqtt/issues/20725

Solution description

Please add the device (manufacturer) to the supported list.

Screenshots/Video

Screenshots/Video [Paste/upload your media here]

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", "0x0004", "0x0005", "0xef00" ], "output_clusters": [ "0x000a", "0x0019" ] }, "242": { "profile_id": "0xa1e0", "device_type": "0x0061", "input_clusters": [], "output_clusters": [ "0x0021" ] } }, "manufacturer": "_TZE200_yia0p3tr", "model": "TS0601", "class": "zigpy.device.Device" }```

Diagnostic information

Diagnostic information ```json { "home_assistant": { "installation_type": "Home Assistant OS", "version": "2024.3.3", "dev": false, "hassio": true, "virtualenv": false, "python_version": "3.12.2", "docker": true, "arch": "x86_64", "timezone": "America/Chicago", "os_name": "Linux", "os_version": "6.6.20-haos", "supervisor": "2024.03.1", "host_os": "Home Assistant OS 12.1", "docker_version": "24.0.7", "chassis": "embedded", "run_as_root": true }, "custom_components": {}, "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", "import_executor": true, "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.38.1", "pyserial==3.5", "pyserial-asyncio==0.6", "zha-quirks==0.0.112", "zigpy-deconz==0.23.1", "zigpy==0.63.4", "zigpy-xbee==0.20.1", "zigpy-zigate==0.12.0", "zigpy-znp==0.12.1", "universal-silabs-flasher==0.0.18", "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*" } ], "is_built_in": true }, "data": { "ieee": "**REDACTED**", "nwk": 7278, "manufacturer": "_TZE200_yia0p3tr", "model": "TS0601", "name": "_TZE200_yia0p3tr TS0601", "quirk_applied": false, "quirk_class": "zigpy.device.Device", "quirk_id": null, "manufacturer_code": 4417, "power_source": "Mains", "lqi": 188, "rssi": -53, "last_seen": "2024-03-31T21:18:14", "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", "0x0004", "0x0005", "0xef00" ], "output_clusters": [ "0x000a", "0x0019" ] }, "242": { "profile_id": "0xa1e0", "device_type": "0x0061", "input_clusters": [], "output_clusters": [ "0x0021" ] } }, "manufacturer": "_TZE200_yia0p3tr", "model": "TS0601" }, "active_coordinator": false, "entities": [ { "entity_id": "update.living_room_curtain_firmware", "name": "_TZE200_yia0p3tr TS0601" } ], "neighbors": [], "routes": [], "endpoint_names": [ { "name": "SMART_PLUG" }, { "name": "PROXY_BASIC" } ], "user_given_name": "living_room_curtain", "device_reg_id": "6ce76bbd2e5b89c32a89a20d4cbfe8e7", "area_id": "living_room", "cluster_details": { "1": { "device_type": { "name": "SMART_PLUG", "id": 81 }, "profile_id": 260, "in_clusters": { "0x0004": { "endpoint_attribute": "groups", "attributes": {}, "unsupported_attributes": {} }, "0x0005": { "endpoint_attribute": "scenes", "attributes": {}, "unsupported_attributes": {} }, "0xef00": { "endpoint_attribute": null, "attributes": {}, "unsupported_attributes": {} }, "0x0000": { "endpoint_attribute": "basic", "attributes": { "0x0001": { "attribute_name": "app_version", "value": 70 }, "0x0004": { "attribute_name": "manufacturer", "value": "_TZE200_yia0p3tr" }, "0x0005": { "attribute_name": "model", "value": "TS0601" } }, "unsupported_attributes": {} } }, "out_clusters": { "0x0019": { "endpoint_attribute": "ota", "attributes": { "0x0002": { "attribute_name": "current_file_version", "value": 70 } }, "unsupported_attributes": {} }, "0x000a": { "endpoint_attribute": "time", "attributes": {}, "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 [Paste your custom quirk here] ```

Additional information

No response

javicalle commented 6 months ago

According to Z2M it must be added to the TuyaMoesCover0601 quirk:

Are you able to check it?

hami89 commented 6 months ago

Even before I found that post, I tried to make a custom quirk, but there were some problems.

He is right that the TuyaMoesCover0601 is the closest one to it, but my device has a secondary endpoint (242).

Simply adding it to the TuyaMoesCover0601 the integration logs an error upon startup, that it is considering my custom version of the quirk, but there is a signature mismatch, as TuyaMoesCover0601 does not define endpoint 242.

So after looking around in the repos, I tried to find the proper definition for 242.

I ended up adding an extra import to the quirk:

from zigpy.zcl.clusters.general import GreenPowerProxy

and then I added this modified version:

class TuyaHTCover0601(TuyaWindowCover):
    """Tuya blind controller device."""

    signature = {
        # "node_descriptor": "NodeDescriptor(byte1=2, byte2=64, mac_capability_flags=128, manufacturer_code=4098,
        #                    maximum_buffer_size=82, maximum_incoming_transfer_size=82, server_mask=11264,
        #                    maximum_outgoing_transfer_size=82, descriptor_capability_field=0)",
        # "endpoints": {
        # "1": { "profile_id": 260, "device_type": "0x0051", "in_clusters": [ "0x0000", "0x0004","0x0005","0xef00"], "out_clusters": ["0x000a","0x0019"] }
        # },
        # "manufacturer": "_TZE200_zah67ekd",
        # "model": "TS0601",
        # "class": "zigpy.device.Device"
        # }
        MODELS_INFO: [
            ("_TZE200_yia0p3tr", "TS0601"),
        ],
        ENDPOINTS: {
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    TuyaManufCluster.cluster_id,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            242: {
                PROFILE_ID: 41440,
                DEVICE_TYPE: 97,
                INPUT_CLUSTERS: [],
                OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
            },
        }
    }

    replacement = {
        ENDPOINTS: {
            1: {
                DEVICE_TYPE: zha.DeviceType.WINDOW_COVERING_DEVICE,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    TuyaManufacturerWindowCover,
                    TuyaWindowCoverControl,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            242: {
                PROFILE_ID: 41440,
                DEVICE_TYPE: 97,
                INPUT_CLUSTERS: [],
                OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
            },
        }
    }

This way the device is identified, I got a new entity in HA, but unfortunately does not work, I got these errors: On UI: "Failed to call service cover/open_cover. Failed to open cover: <Status.UNSUP_MANUF_CLUSTER_COMMAND: 131>" In logs:

2024-04-01 09:32:29.900 DEBUG (MainThread) [zigpy.util] Tries remaining: 3
2024-04-01 09:32:29.900 DEBUG (MainThread) [zhaquirks.tuya] a4:c1:38:9d:b4:d6:69:28 Sending Tuya Cluster Command.. Manufacturer is _TZE200_yia0p3tr Cluster Command is 0x0001, Arguments are ()
2024-04-01 09:32:29.901 DEBUG (MainThread) [zhaquirks.tuya] a4:c1:38:9d:b4:d6:69:28 Sending Tuya Command. Paylod values [endpoint_id : 1, Status : 0, TSN: 0, Command: 0x0401, Function: 0, Data: [1, 2]]
2024-04-01 09:32:29.902 DEBUG (MainThread) [zigpy.zcl] [0xBBD8:1:0xef00] Sending request header: ZCLHeader(frame_control=FrameControl<0x05>(frame_type=<FrameType.CLUSTER_COMMAND: 1>, is_manufacturer_specific=True, direction=<Direction.Client_to_Server: 0>, disable_default_response=0, reserved=0, *is_cluster=True, *is_general=False), manufacturer=4417, tsn=3, command_id=0, *direction=<Direction.Client_to_Server: 0>)
2024-04-01 09:32:29.902 DEBUG (MainThread) [zigpy.zcl] [0xBBD8:1:0xef00] Sending request: set_data(param=Command(status=0, tsn=0, command_id=1025, function=0, data=[1, 2]))
2024-04-01 09:32:29.903 DEBUG (MainThread) [bellows.zigbee.application] Sending packet ZigbeePacket(timestamp=datetime.datetime(2024, 4, 1, 14, 32, 29, 903563, tzinfo=datetime.timezone.utc), src=AddrModeAddress(addr_mode=<AddrMode.NWK: 2>, address=0x0000), src_ep=1, dst=AddrModeAddress(addr_mode=<AddrMode.NWK: 2>, address=0xBBD8), dst_ep=1, source_route=None, extended_timeout=False, tsn=3, profile_id=260, cluster_id=61184, data=Serialized[b'\x05A\x11\x03\x00\x00\x00\x01\x04\x00\x01\x02'], tx_options=<TransmitOptions.NONE: 0>, radius=0, non_member_radius=0, lqi=None, rssi=None)
2024-04-01 09:32:29.903 DEBUG (MainThread) [bellows.ezsp.protocol] Send command sendUnicast: (<EmberOutgoingMessageType.OUTGOING_DIRECT: 0>, 0xbbd8, EmberApsFrame(profileId=260, clusterId=61184, sourceEndpoint=1, destinationEndpoint=1, options=<EmberApsOption.APS_OPTION_RETRY|APS_OPTION_ENABLE_ROUTE_DISCOVERY: 320>, groupId=0, sequence=3), 15, b'\x05A\x11\x03\x00\x00\x00\x01\x04\x00\x01\x02')
2024-04-01 09:32:29.914 DEBUG (MainThread) [bellows.ezsp.protocol] Application frame received sendUnicast: [<EmberStatus.SUCCESS: 0>, 0]
2024-04-01 09:32:30.136 DEBUG (MainThread) [bellows.ezsp.protocol] Application frame received messageSentHandler: [<EmberOutgoingMessageType.OUTGOING_DIRECT: 0>, 48088, EmberApsFrame(profileId=260, clusterId=61184, sourceEndpoint=1, destinationEndpoint=1, options=<EmberApsOption.APS_OPTION_RETRY: 64>, groupId=0, sequence=0), 15, <EmberStatus.SUCCESS: 0>, b'']
2024-04-01 09:32:30.136 DEBUG (MainThread) [bellows.zigbee.application] Received messageSentHandler frame with [<EmberOutgoingMessageType.OUTGOING_DIRECT: 0>, 48088, EmberApsFrame(profileId=260, clusterId=61184, sourceEndpoint=1, destinationEndpoint=1, options=<EmberApsOption.APS_OPTION_RETRY: 64>, groupId=0, sequence=0), 15, <EmberStatus.SUCCESS: 0>, b'']
2024-04-01 09:32:30.144 DEBUG (MainThread) [bellows.ezsp.protocol] Application frame received incomingMessageHandler: [<EmberIncomingMessageType.INCOMING_UNICAST: 0>, EmberApsFrame(profileId=260, clusterId=61184, sourceEndpoint=1, destinationEndpoint=1, options=<EmberApsOption.APS_OPTION_ENABLE_ROUTE_DISCOVERY: 256>, groupId=0, sequence=48), 164, -59, 0xbbd8, 255, 255, b'\x18\x03\x0b\x00\x83']
2024-04-01 09:32:30.144 DEBUG (MainThread) [bellows.zigbee.application] Received incomingMessageHandler frame with [<EmberIncomingMessageType.INCOMING_UNICAST: 0>, EmberApsFrame(profileId=260, clusterId=61184, sourceEndpoint=1, destinationEndpoint=1, options=<EmberApsOption.APS_OPTION_ENABLE_ROUTE_DISCOVERY: 256>, groupId=0, sequence=48), 164, -59, 0xbbd8, 255, 255, b'\x18\x03\x0b\x00\x83']
2024-04-01 09:32:30.144 DEBUG (MainThread) [zigpy.application] Received a packet: ZigbeePacket(timestamp=datetime.datetime(2024, 4, 1, 14, 32, 30, 144774, tzinfo=datetime.timezone.utc), src=AddrModeAddress(addr_mode=<AddrMode.NWK: 2>, address=0xBBD8), src_ep=1, dst=AddrModeAddress(addr_mode=<AddrMode.NWK: 2>, address=0x0000), dst_ep=1, source_route=None, extended_timeout=False, tsn=48, profile_id=260, cluster_id=61184, data=Serialized[b'\x18\x03\x0b\x00\x83'], tx_options=<TransmitOptions.NONE: 0>, radius=0, non_member_radius=0, lqi=164, rssi=-59)
2024-04-01 09:32:30.145 DEBUG (MainThread) [zigpy.zcl] [0xBBD8:1:0xef00] Received ZCL frame: b'\x18\x03\x0b\x00\x83'
2024-04-01 09:32:30.146 DEBUG (MainThread) [zigpy.zcl] [0xBBD8:1:0xef00] Decoded ZCL frame header: ZCLHeader(frame_control=FrameControl<0x18>(frame_type=<FrameType.GLOBAL_COMMAND: 0>, is_manufacturer_specific=0, direction=<Direction.Server_to_Client: 1>, disable_default_response=1, reserved=0, *is_cluster=False, *is_general=True), tsn=3, command_id=11, *direction=<Direction.Server_to_Client: 1>)
2024-04-01 09:32:30.146 DEBUG (MainThread) [zigpy.zcl] [0xBBD8:1:0xef00] Decoded ZCL frame: TuyaManufacturerWindowCover:Default_Response(command_id=0, status=<Status.UNSUP_MANUF_CLUSTER_COMMAND: 131>)
2024-04-01 09:32:30.147 ERROR (MainThread) [homeassistant.components.websocket_api.http.connection] [140206046492864] Failed to close cover: <Status.UNSUP_MANUF_CLUSTER_COMMAND: 131>
Traceback (most recent call last):
File "/usr/src/homeassistant/homeassistant/components/websocket_api/commands.py", line 239, in handle_call_service
response = await hass.services.async_call(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/src/homeassistant/homeassistant/core.py", line 2319, in async_call
response_data = await coro
^^^^^^^^^^
File "/usr/src/homeassistant/homeassistant/core.py", line 2356, in _execute_service
return await target(service_call)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/src/homeassistant/homeassistant/helpers/service.py", line 905, in entity_service_call
single_response = await _handle_entity_call(
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/src/homeassistant/homeassistant/helpers/service.py", line 975, in _handle_entity_call
result = await task
^^^^^^^^^^
File "/usr/src/homeassistant/homeassistant/components/zha/cover.py", line 289, in async_close_cover
raise HomeAssistantError(f"Failed to close cover: {res[1]}")
homeassistant.exceptions.HomeAssistantError: Failed to close cover: <Status.UNSUP_MANUF_CLUSTER_COMMAND: 131>

So I probably messed up something along the way.

whatisthisUT commented 6 months ago

@hami89 did you manage to get the _TZE200_yia0p3tr motor running on ZHA?

hami89 commented 5 months ago

@hami89 did you manage to get the _TZE200_yia0p3tr motor running on ZHA?

Unfortunately no, it still does not work.

hami89 commented 5 months ago

One interesting addition is that while I cannot control the motor with my custom quirk, if I operate it with it's 433Mhz remote, it reports the position correctly through zigbee to HA.

hami89 commented 3 months ago

Can you provide any help to figure out what can be the problem with this motor integration? As it works for zigbee2mqtt it probably should work for mine too. I can extract any logs needed and do some testing, but I need guidance.

hami89 commented 1 month ago

@whatisthisUT I managed to make it work, but I don't know how to do it the correct way so I can create a PR from it.

Besides the quirk, I figured that the server commands for this particular model should go out as "is_manufacturer_specific = False", otherwise the device rejects them. Unfortunately, for tuya it si set to True on the module init script.

For testing, I modified the /usr/local/lib/python3.12/site-packages/zha/application/platforms/cover/init.py init script file from a root shell in HA. After restart, it works as expected.

Can this be achieved from the quirk itself? I got lost in the class hierarchy of zha/zigpy. I would appreciate some guidance.

whatisthisUT commented 1 month ago

Hey @hami89 awesome that you managed to make it work. I will try to replicated that on my instance this week if I find time. I'm not involved in development for IOT so I can't be a lot of help to you except for testing.