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
750 stars 685 forks source link

[Device Support Request] Tuya water timer (TS0049 by _TZ3000_gjpgagal) #3316

Open gunarser opened 2 months ago

gunarser commented 2 months ago

Problem description

Please can a handler be created to support a Tuya water timer (TS0049). The device can be added to home assistant via ZHA but not all entities (like timer which by default is set to 10 minutes) are available to control the valve.

Solution description

Creation of a new handler that supports the device

Screenshots/Video

image

Device signature

Device signature ```json { "node_descriptor": { "logical_type": 2, "complex_descriptor_available": 0, "user_descriptor_available": 0, "reserved": 0, "aps_flags": 0, "frequency_band": 8, "mac_capability_flags": 128, "manufacturer_code": 4417, "maximum_buffer_size": 66, "maximum_incoming_transfer_size": 66, "server_mask": 10752, "maximum_outgoing_transfer_size": 66, "descriptor_capability_field": 0 }, "endpoints": { "1": { "profile_id": "0x0104", "device_type": "0x0000", "input_clusters": [ "0x0000", "0x0001", "0x0003", "0x0004", "0x0005", "0x0006", "0xe001" ], "output_clusters": [ "0x000a", "0x0019" ] } }, "manufacturer": "_TZ3000_gjpgagal", "model": "TS0049", "class": "zigpy.device.Device" } ```

Diagnostic information

Diagnostic information ```json { "home_assistant": { "installation_type": "Home Assistant OS", "version": "2024.8.2", "dev": false, "hassio": true, "virtualenv": false, "python_version": "3.12.4", "docker": true, "arch": "aarch64", "timezone": "Europe/Riga", "os_name": "Linux", "os_version": "6.6.31-haos-raspi", "supervisor": "2024.08.0", "host_os": "Home Assistant OS 13.1", "docker_version": "26.1.4", "chassis": "embedded", "run_as_root": true }, "custom_components": { "attributes": { "documentation": "https://github.com/pilotak/homeassistant-attributes", "version": "1.2.1", "requirements": [] }, "mikrotik_router": { "documentation": "https://github.com/tomaae/homeassistant-mikrotik_router", "version": "0.0.0", "requirements": [ "librouteros>=3.2.0", "mac-vendor-lookup>=0.1.12" ] }, "salusfy": { "documentation": "https://github.com/floringhimie/salusfy", "version": "0.0.1", "requirements": [] }, "sunspec": { "documentation": "https://github.com/cjne/ha-sunspec", "version": "0.0.26", "requirements": [ "pysunspec2==1.1.5" ] }, "home_connect_alt": { "documentation": "https://github.com/ekutner/home-connect-hass", "version": "1.1.7", "requirements": [ "home-connect-async==0.8.0" ] }, "frigate": { "documentation": "https://github.com/blakeblackshear/frigate", "version": "5.3.0", "requirements": [ "pytz" ] }, "uptime_kuma": { "documentation": "https://github.com/meichthys/uptime_kuma/blob/main/README.md", "version": "2.1.0", "requirements": [ "pyuptimekuma-hass" ] }, "localtuya": { "documentation": "https://github.com/xZetsubou/hass-localtuya/", "version": "2024.7.0", "requirements": [] }, "nordpool": { "documentation": "https://github.com/custom-components/nordpool/", "version": "0.0.14", "requirements": [ "nordpool>=0.2", "backoff" ] }, "hacs": { "documentation": "https://hacs.xyz/docs/configuration/start", "version": "2.0.0", "requirements": [ "aiogithubapi>=22.10.1" ] }, "ssh_command": { "documentation": "https://github.com/AlexxIT/SSHCommand", "version": "1.1.0", "requirements": [ "paramiko" ] }, "watchman": { "documentation": "https://github.com/dummylabs/thewatchman", "version": "0.6.3", "requirements": [ "prettytable==3.10.0" ] } }, "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", "zha", "universal_silabs_flasher" ], "requirements": [ "universal-silabs-flasher==0.0.22", "zha==0.0.31" ], "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": 7.906100000809602e-05 }, "9a224a950316e4e06044c03d77858d2c": { "wait_import_platforms": -0.011965733000010914, "config_entry_setup": 13.179298865000021 } }, "data": { "ieee": "**REDACTED**", "nwk": 49933, "manufacturer": "_TZ3000_gjpgagal", "model": "TS0049", "name": "_TZ3000_gjpgagal TS0049", "quirk_applied": false, "quirk_class": "zigpy.device.Device", "quirk_id": null, "manufacturer_code": 4417, "power_source": "Battery or Unknown", "lqi": 56, "rssi": -86, "last_seen": "2024-08-25T16:35:05", "available": true, "device_type": "EndDevice", "signature": { "node_descriptor": { "logical_type": 2, "complex_descriptor_available": 0, "user_descriptor_available": 0, "reserved": 0, "aps_flags": 0, "frequency_band": 8, "mac_capability_flags": 128, "manufacturer_code": 4417, "maximum_buffer_size": 66, "maximum_incoming_transfer_size": 66, "server_mask": 10752, "maximum_outgoing_transfer_size": 66, "descriptor_capability_field": 0 }, "endpoints": { "1": { "profile_id": "0x0104", "device_type": "0x0000", "input_clusters": [ "0x0000", "0x0001", "0x0003", "0x0004", "0x0005", "0x0006", "0xe001" ], "output_clusters": [ "0x000a", "0x0019" ] } }, "manufacturer": "_TZ3000_gjpgagal", "model": "TS0049" }, "active_coordinator": false, "entities": [ { "entity_id": "button.tz3000_gjpgagal_ts0049_identify", "name": "_TZ3000_gjpgagal TS0049" }, { "entity_id": "sensor.tz3000_gjpgagal_ts0049_battery", "name": "_TZ3000_gjpgagal TS0049" }, { "entity_id": "switch.tz3000_gjpgagal_ts0049_switch", "name": "_TZ3000_gjpgagal TS0049" }, { "entity_id": "update.tz3000_gjpgagal_ts0049_firmware", "name": "_TZ3000_gjpgagal TS0049" }, { "entity_id": "sensor.water_timer_battery", "name": "_TZ3000_gjpgagal TS0049" }, { "entity_id": "update.water_timer_firmware", "name": "_TZ3000_gjpgagal TS0049" }, { "entity_id": "button.water_timer_identify", "name": "_TZ3000_gjpgagal TS0049" }, { "entity_id": "switch.water_timer_switch", "name": "_TZ3000_gjpgagal TS0049" } ], "neighbors": [], "routes": [], "endpoint_names": [ { "name": "ON_OFF_SWITCH" } ], "user_given_name": "Water timer", "device_reg_id": "2aa680f6ef3d23419a787e148e5310a1", "area_id": "darzs", "cluster_details": { "1": { "device_type": { "name": "ON_OFF_SWITCH", "id": 0 }, "profile_id": 260, "in_clusters": { "0x0003": { "endpoint_attribute": "identify", "attributes": {}, "unsupported_attributes": {} }, "0x0004": { "endpoint_attribute": "groups", "attributes": {}, "unsupported_attributes": {} }, "0x0005": { "endpoint_attribute": "scenes", "attributes": {}, "unsupported_attributes": {} }, "0x0001": { "endpoint_attribute": "power", "attributes": { "0x0021": { "attribute_name": "battery_percentage_remaining", "value": 200 }, "0x0020": { "attribute_name": "battery_voltage", "value": 30 } }, "unsupported_attributes": { "0x0031": { "attribute_name": "battery_size" }, "0x0033": { "attribute_name": "battery_quantity" } } }, "0x0006": { "endpoint_attribute": "on_off", "attributes": { "0x4002": { "attribute_name": "off_wait_time", "value": 0 }, "0x0000": { "attribute_name": "on_off", "value": 0 }, "0x4001": { "attribute_name": "on_time", "value": 0 } }, "unsupported_attributes": { "0x4003": { "attribute_name": "start_up_on_off" } } }, "0xe001": { "endpoint_attribute": null, "attributes": {}, "unsupported_attributes": {} }, "0x0000": { "endpoint_attribute": "basic", "attributes": { "0x0004": { "attribute_name": "manufacturer", "value": "_TZ3000_gjpgagal" }, "0x0005": { "attribute_name": "model", "value": "TS0049" } }, "unsupported_attributes": {} } }, "out_clusters": { "0x0019": { "endpoint_attribute": "ota", "attributes": { "0x0002": { "attribute_name": "current_file_version", "value": 73 } }, "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

I have a Tuya Zigbee gateway at my disposal that could be used for further troubleshooting if necessary.

danpeig commented 2 months ago

Look at issue #2377

gunarser commented 2 months ago

Look at issue #2377

If that was meant for me then I already tried the quirk and commented on the results here: https://github.com/zigpy/zha-device-handlers/issues/2377#issuecomment-2258177255 It looks like there are some incompatibility issues, i.e. the device is quite different from the one the quirk was made for.

danpeig commented 2 months ago

2377 is the thread for TS0049. Developers that work with this type of device are subscribed to that issue.

As far as I am aware of, Tuya has only one specification for TS0049.

Most user problems are related with binding the quirk to the device because different manufacturers have different signatures.

gunarser commented 2 months ago

OK. Perhaps I was doing something wrong. I changed the manufacturer as per my comment but the quirk does not get loaded. What is the best way to troubleshoot such cases?

danpeig commented 2 months ago

Do you have other custom quirks installed and working? I think the first step is ensure the system is actually loading the code.

My recommendation is use a quirk for a ZigBee device you already have working like the TS0001 or TS0011 (wall plugs and light switches) and see if it loads properly. The custom quirk will appear in the device information page.

Once the quirk are loading, then you can force the 0049 to bind to your specific device.

gunarser commented 2 months ago

I have Nous light switches that require quirk and some time back I installed it manually and it was correctly loaded. But for some time the quirk is included in HA and added automatically. So I removed the custom quirk. I don't have any other device that need custom quirk. Can I use those light switches for a test even though the quirk is already in HA?

This is how swithes look like in HA:

image

danpeig commented 2 months ago

If you have a custom quirk it will be loaded instead of the built in. This is just to test the environment.

If it loads, then it's just about fingerprinting your device properly.

You can change the TS001x quirk (known to be working) to see if you manage to attach it to the water valve. The valve will obviously no work, but you will know the binding code.

Regards

gunarser commented 2 months ago

I added the ts001x.py file to the config/custom_zha_quirks/ directory and it looks like the custom quirk is indeed loaded for light switches instead of the built-in one: image

I modified the ts001x.py file by changing model ID of 3-gang switch from TS0013 to TS0049 but the quirk does not get loaded after HA restart. Should I re-pair the water timer to get the quirk loaded or is there something else that needs to be done in order to get the ts001x loaded?

danpeig commented 1 month ago

The quirk should still be loading for the other devices. If it is not, you have a syntax problem with the code.

If it loads but not binds yo the valve, you have a problem with the "signature". The signature must match your valve device in order for it to bind. The device name is only one parameter of the signature (see https://github.com/zigpy/zha-device-handlers).

gunarser commented 1 month ago

I changed almost all the parameters in the signature both in TS0049.py and ts001x.py but none of the quirks gets loaded (at least they do not appear under Zigbee info tab). How much of the signature must match in order to get quirk loaded?

This is the signature section of the modified TS0049 quirk:

class TuyaIrrigationValve(EnchantedDevice):
    """Tuya green irrigation valve device."""
    signature = {
        MODELS_INFO: [("_TZ3000_gjpgagal", "TS0049")],
        ENDPOINTS: {
            # <SimpleDescriptor endpoint=1 profile=260 device_type=0
            # device_version=1
            # input_clusters=[0, 1, 3, 4, 5, 6, 57345]
            # output_clusters=[10, 25]>
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.ON_OFF_SWITCH,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Power.cluster_id,
                    Identity.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    OnOff.cluster_id,
                    Null.cluster_id,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
        },
    }

    replacement = {
        ENDPOINTS: {
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.ON_OFF_SWITCH,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Power.cluster_id,
                    Identity.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    TuyaOnOff,
                    Null.cluster_id,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
        },
    }`

This is how the device signature looks like in 'Manage Zigbee device' window:

{
  "node_descriptor": {
    "logical_type": 2,
    "complex_descriptor_available": 0,
    "user_descriptor_available": 0,
    "reserved": 0,
    "aps_flags": 0,
    "frequency_band": 8,
    "mac_capability_flags": 128,
    "manufacturer_code": 4417,
    "maximum_buffer_size": 66,
    "maximum_incoming_transfer_size": 66,
    "server_mask": 10752,
    "maximum_outgoing_transfer_size": 66,
    "descriptor_capability_field": 0
  },
  "endpoints": {
    "1": {
      "profile_id": "0x0104",
      "device_type": "0x0000",
      "input_clusters": [
        "0x0000",
        "0x0001",
        "0x0003",
        "0x0004",
        "0x0005",
        "0x0006",
        "0xe001"
      ],
      "output_clusters": [
        "0x000a",
        "0x0019"
      ]
    }
  },
  "manufacturer": "_TZ3000_gjpgagal",
  "model": "TS0049",
  "class": "zigpy.device.Device"
}
danpeig commented 1 month ago

If you use the text below in the signature it should bind:

"profile_id": "0x0104", "device_type": "0x0000", "input_clusters": [ "0x0000", "0x0001", "0x0003", "0x0004", "0x0005", "0x0006", "0xe001" ], "output_clusters": [ "0x000a", "0x0019" ]

gunarser commented 1 month ago

I finally managed to load and attach a custom quirk. I used this quirk-generator For beginners it is a great tool to get started. Now the device info looks like this:

image

Now the question is how to add the missing attributes like timer setting to the quirk?

danpeig commented 1 month ago

Great! Now you just need to get the replacements to work correctly. The timer will be set using the "Manage Zigbee Device" -> "Valve family cluster" -> irrigation_time. The On-Off switch will be exposed in the interface.

gunarser commented 1 month ago

Should the TS0049 quirk provide the needed "Valve family cluster" or there is other quirk that should be used as an example? This is what I see under clusters (no value is read from the irrigation_time attribute):

image image image

danpeig commented 1 month ago

Yes. It is probably not mapped correctly.

gunarser commented 1 month ago

What would be the procedure to get the right mappings? Do I need Tuya Zigbee gateway for that?

danpeig commented 1 month ago

The attribute mapping is the final step to get the valve working. There is no need to use a Tuya brand ZigBee router.

This link has the attribute map specs for the valve: https://developer.tuya.com/en/docs/connect-subdevices-to-gateways/Zigbee-Water-valve-controller-access-standard?id=Kbahvojbqzvgl

gunarser commented 1 month ago

I understand that I can additionally map/read/control the following attributes: DP4 Fault report DP11 Irrigation time DP12 Work state and perhaps DP15 Once using time

But I don't quite understand how that mapping is done via the quirk. Which lines variables need to be adjusted?

danpeig commented 1 month ago

The data points are numbers, in the original code: 26, 101, 110, 111...etc.

Clusters are hexadecimal: 0xEF01, 0xEF02...

First you must set the clusters, then the DP.

gunarser commented 1 month ago

I modified the following lines in the quirk:

class TuyaValveFamilyCluster(TuyaMCUCluster):
    """On/Off Tuya family cluster with extra device attributes"""

    attributes = TuyaMCUCluster.attributes.copy()
    attributes.update(
        {
            0xEF01: ("countdown", t.uint32_t, True),
            0xEF02: ("work_state", t.uint32_t, True),
            0xEF03: ("fault", t.uint32_t, True),
        }
    )

    dp_to_attribute: Dict[int, DPToAttributeMapping] = {
        4: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "fault",
        ),
        11: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "countdown",
        ),
        12: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "work_state",
        ),
    }

    data_point_handlers = {
        4: "_dp_2_attr_update",
        11: "_dp_2_attr_update",
        12: "_dp_2_attr_update",
    }

But I get no values in the corresponding attributes:

image

Value is none for all.

gunarser commented 1 month ago

What am I doing wrong? Perhaps cluster numbers are defined wrong? I am total newbie to quirks, don't know how these mappings work.

I modified the following lines in the quirk:

class TuyaValveFamilyCluster(TuyaMCUCluster):
    """On/Off Tuya family cluster with extra device attributes"""

    attributes = TuyaMCUCluster.attributes.copy()
    attributes.update(
        {
            0xEF01: ("countdown", t.uint32_t, True),
            0xEF02: ("work_state", t.uint32_t, True),
            0xEF03: ("fault", t.uint32_t, True),
        }
    )

    dp_to_attribute: Dict[int, DPToAttributeMapping] = {
        4: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "fault",
        ),
        11: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "countdown",
        ),
        12: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "work_state",
        ),
    }

    data_point_handlers = {
        4: "_dp_2_attr_update",
        11: "_dp_2_attr_update",
        12: "_dp_2_attr_update",
    }

But I get no values in the corresponding attributes:

image

Value is none for all.

danpeig commented 1 month ago

Is your valve supported by ZigBee 2MQTT? For the other model, we used the mapping information from this tool. Otherwise it is trial and error.

gunarser commented 1 month ago

I don't know. I found the following Github issues that might relate to my valve: https://github.com/Koenkk/zigbee2mqtt/issues/23191 https://github.com/Koenkk/zigbee2mqtt/issues/16671 https://github.com/Koenkk/zigbee2mqtt/issues/22950 https://github.com/Koenkk/zigbee2mqtt/issues/21788

Should I install Z2M in order to understand whether my specific device is supported?

Is your valve supported by ZigBee 2MQTT? For the other model, we used the mapping information from this tool. Otherwise it is trial and error.

danpeig commented 1 month ago

I just got the Z2M source code.

gunarser commented 1 month ago

Good! Tell if anything is needed from me. As I mentioned before I have a spare Tuya Zigbee gateway - could do some additional troubleshooting if necessary.

I just got the Z2M source code.

gunarser commented 1 month ago

In the meantime, I installed Z2M Docker container and paired the valve to it:

image

image

image

Should I collect some more info from Z2M in order to create ZHA quirk?

danpeig commented 1 month ago

Z2M has this device properly mapped. You need to translate it to ZHA. This is what we did with the other valve. Unfortunately I don't remember how exactly that worked.

gunarser commented 1 month ago

OK. Who might remember?

danpeig commented 1 month ago

Whoever committed this valve map to Z2M.

gunarser commented 1 month ago

I connected the valve to Tuya Zigbee gateway and retrieved DP IDs as described in the following document: https://www.zigbee2mqtt.io/advanced/support-new-devices/03_find_tuya_data_points.html

They were:

{"1":"Switch",
"4":"Failure to report",
"7":"Battery",
"9":"Accumulated usage time",
"10":"Weather Delay",
"11":"Irrigation time",
"12":"work state",
"15":"Once using time",
"16":"Cycle irrigation",
"17":"Normal  timer",
"29":"Next irrigation time"}

Basically they are the same as described in Tuya document: https://developer.tuya.com/en/docs/connect-subdevices-to-gateways/Zigbee-Water-valve-controller-access-standard?id=Kbahvojbqzvgl

So the DPs are correct. The question is why the quirk does not map them correctly. What am I missing?!

gunarser commented 1 month ago

Here is the complete modified quirk:

from typing import Any, Dict, Optional, Union

import zigpy.types as t
#from zhaquirks import DoublingPowerConfigurationCluster
from zhaquirks.const import (
    DEVICE_TYPE,
    ENDPOINTS,
    INPUT_CLUSTERS,
    MODELS_INFO,
    OUTPUT_CLUSTERS,
    PROFILE_ID,
)
from zhaquirks.tuya import (
    TUYA_SEND_DATA, TuyaLocalCluster,
)
from zhaquirks.tuya.mcu import (
    DPToAttributeMapping,
    EnchantedDevice,
    TuyaMCUCluster,
#    TuyaOnOff,
)
from zigpy.profiles import zha
from zigpy.zcl import foundation
from zigpy.zcl.clusters.general import Basic, Groups, Identify, OnOff, Ota, Time, PowerConfiguration, Scenes

#class TuyaValveFamilyBattery(TuyaLocalCluster, DoublingPowerConfigurationCluster):
#    _values = [10, 50, 90]
#    _CONSTANT_ATTRIBUTES = {
#        PowerConfiguration.attributes_by_name["battery_quantity"].id: 4,
#        PowerConfiguration.attributes_by_name["battery_size"].id: PowerConfiguration.BatterySize.AAA
#    }
#
#    def _update_attribute(self, attrid, value):
#        if attrid == self.BATTERY_PERCENTAGE_REMAINING:
#            value = self._values[value]
#        super()._update_attribute(attrid, value)

class TuyaValveFamilyCluster(TuyaMCUCluster):
    """On/Off Tuya family cluster with extra device attributes"""

    attributes = TuyaMCUCluster.attributes.copy()
    attributes.update(
        {
            0xEF01: ("countdown", t.uint32_t, True),
            0xEF02: ("work_state", t.enum8, True),
            0xEF03: ("fault", t.uint32_t, True),
        }
    )

    async def command(
            self,
            command_id: Union[foundation.GeneralCommand, int, t.uint8_t],
            *args,
            manufacturer: Optional[Union[int, t.uint16_t]] = None,
            expect_reply: bool = True,
            tsn: Optional[Union[int, t.uint8_t]] = None,
            **kwargs: Any,
    ):
        """Override the default Cluster command."""
        self.debug("Setting the NO manufacturer id in command: %s", command_id)
        return await super().command(
            TUYA_SEND_DATA,
            *args,
            manufacturer=foundation.ZCLHeader.NO_MANUFACTURER_ID,
            expect_reply=expect_reply,
            tsn=tsn,
            **kwargs,
        )

    dp_to_attribute: Dict[int, DPToAttributeMapping] = {
        4: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "fault",
        ),
        11: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "countdown",
        ),
        12: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "work_state",
        ),
    }

    data_point_handlers = {
        4: "_dp_2_attr_update",
        11: "_dp_2_attr_update",
        12: "_dp_2_attr_update",
    }

class TuyaIrrigationValve(EnchantedDevice):
    """Tuya green irrigation valve device."""
    signature = {
        MODELS_INFO: [("_TZ3000_gjpgagal", "TS0049")],
        ENDPOINTS: {
            # <SimpleDescriptor endpoint=1 profile=260 device_type=0
            # device_version=1
            # input_clusters=[0, 61184]
            # output_clusters=[10, 25]>
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.ON_OFF_SWITCH,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    PowerConfiguration.cluster_id,
                    Identify.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    OnOff.cluster_id,
                    0xe001,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
        },
    }

    replacement = {
        ENDPOINTS: {
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.ON_OFF_SWITCH,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    PowerConfiguration.cluster_id,
                    Identify.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    OnOff.cluster_id,
                    TuyaValveFamilyCluster,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
        },
    }

I commented out attribute definitions that are there already out-of-box like on/off switch. Perhaps the issue is with this section:

    async def command(
            self,
            command_id: Union[foundation.GeneralCommand, int, t.uint8_t],
            *args,
            manufacturer: Optional[Union[int, t.uint16_t]] = None,
            expect_reply: bool = True,
            tsn: Optional[Union[int, t.uint8_t]] = None,
            **kwargs: Any,
    ):
        """Override the default Cluster command."""
        self.debug("Setting the NO manufacturer id in command: %s", command_id)
        return await super().command(
            TUYA_SEND_DATA,
            *args,
            manufacturer=foundation.ZCLHeader.NO_MANUFACTURER_ID,
            expect_reply=expect_reply,
            tsn=tsn,
            **kwargs,
        )