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 636 forks source link

Tuya water valve (TS0049 by _TZ3210_0jxeoadc) #2377

Open pcxsam opened 1 year ago

pcxsam commented 1 year ago

Problem description

Please can a handler be created to support a Tuya water valve controller (TS0049). The device can be added to home assistant via zha but no entities are available to control the valve.

Solution description

Creation of a new handler to support the device

Screenshots/Video

Screenshots/Video ![Screenshot 2023-05-04 115003](https://user-images.githubusercontent.com/76849001/236183267-659bc96c-4ac7-44f0-b554-164eda30417e.png)

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": 260, "device_type": "0x0000", "in_clusters": [ "0x0000", "0xef00" ], "out_clusters": [ "0x000a", "0x0019" ] } }, "manufacturer": "_TZ3210_0jxeoadc", "model": "TS0049", "class": "zigpy.device.Device" } ```

Diagnostic information

Diagnostic information ```json { "home_assistant": { "installation_type": "Home Assistant OS", "version": "2023.4.6", "dev": false, "hassio": true, "virtualenv": false, "python_version": "3.10.10", "docker": true, "arch": "aarch64", "timezone": "Europe/London", "os_name": "Linux", "os_version": "6.1.25", "supervisor": "2023.04.1", "host_os": "Home Assistant OS 10.1", "docker_version": "23.0.3", "chassis": "embedded", "run_as_root": true }, "custom_components": { "fullykiosk": { "version": "1.1.0", "requirements": [ "python-fullykiosk==0.0.11" ] }, "watchman": { "version": "0.5.1", "requirements": [ "prettytable==3.0.0" ] }, "hacs": { "version": "1.32.1", "requirements": [ "aiogithubapi>=22.10.1" ] }, "alexa_media": { "version": "4.6.2", "requirements": [ "alexapy==1.26.5", "packaging>=20.3", "wrapt>=1.12.1" ] }, "platerecognizer": { "version": "1.0.0", "requirements": [ "pillow", "requests" ] }, "webrtc": { "version": "v3.1.0", "requirements": [] }, "govee": { "version": "0.2.2", "requirements": [ "govee-api-laggat==0.2.2", "dacite==1.6.0" ] }, "myenergi": { "version": "0.0.23", "requirements": [ "pymyenergi==0.0.27" ] }, "frigate": { "version": "4.0.0", "requirements": [ "pytz==2022.7" ] }, "tplink_deco": { "version": "3.3.1", "requirements": [ "pycryptodome>=3.12.0" ] }, "solaredge_modbus_multi": { "version": "2.2.14", "requirements": [ "pymodbus>=3.1.1" ] }, "feedparser": { "version": "0.1.7", "requirements": [ "feedparser==6.0.8" ] }, "samsungtv_smart": { "version": "0.11.6", "requirements": [ "websocket-client!=1.4.0,>=0.58.0", "wakeonlan>=2.0.0", "aiofiles>=0.8.0", "casttube>=0.2.1" ] } }, "integration_manifest": { "domain": "zha", "name": "Zigbee Home Automation", "after_dependencies": [ "onboarding", "usb" ], "codeowners": [ "@dmulcahey", "@adminiuga", "@puddly" ], "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" ], "requirements": [ "bellows==0.35.1", "pyserial==3.5", "pyserial-asyncio==0.6", "zha-quirks==0.0.97", "zigpy-deconz==0.20.0", "zigpy==0.54.1", "zigpy-xbee==0.17.0", "zigpy-zigate==0.10.3", "zigpy-znp==0.10.0" ], "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": "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": "_slzb-06._tcp.local.", "name": "slzb-06*" } ], "is_built_in": true }, "data": { "ieee": "**REDACTED**", "nwk": 59523, "manufacturer": "_TZ3210_0jxeoadc", "model": "TS0049", "name": "_TZ3210_0jxeoadc TS0049", "quirk_applied": false, "quirk_class": "zigpy.device.Device", "manufacturer_code": 4417, "power_source": "Battery or Unknown", "lqi": 138, "rssi": null, "last_seen": "2023-05-04T11:53:44", "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": 260, "device_type": "0x0000", "in_clusters": [ "0x0000", "0xef00" ], "out_clusters": [ "0x000a", "0x0019" ] } } }, "active_coordinator": false, "entities": [], "neighbors": [], "routes": [], "endpoint_names": [ { "name": "ON_OFF_SWITCH" } ], "user_given_name": null, "device_reg_id": "298a89d2209617fae104b31fe9fcf0f8", "area_id": "outside", "cluster_details": { "1": { "device_type": { "name": "ON_OFF_SWITCH", "id": 0 }, "profile_id": 260, "in_clusters": { "0xef00": { "endpoint_attribute": null, "attributes": {}, "unsupported_attributes": {} }, "0x0000": { "endpoint_attribute": "basic", "attributes": { "0x0004": { "attribute_name": "manufacturer", "value": "_TZ3210_0jxeoadc" }, "0x0005": { "attribute_name": "model", "value": "TS0049" } }, "unsupported_attributes": {} } }, "out_clusters": { "0x0019": { "endpoint_attribute": "ota", "attributes": {}, "unsupported_attributes": {} }, "0x000a": { "endpoint_attribute": "time", "attributes": {}, "unsupported_attributes": {} } } } } } } ```
idem2lyon commented 1 year ago

It would be great, I have same device !!

fouram commented 1 year ago

I just bought one of these as well, hoping to get it working. Seems like not yet! Would love to have support, as a renter its nearly impossible to get a hose valve I can simply switch on and off without HACS and cloud services and timings and checking weather schedules (looking at you, Orbit). My setup would greatly benefit from the simplicity of my end points just being on/off switches!

DonnySus commented 1 year ago

I also bought one of these irrigation valves. ZHA has not a quirk for a moment (at least I found nothing), but Zigbee2MQTT already developed some workaround. Can anybody from development team check following link it the provided code helo to build up a quirk for that device? The link for Zigbee2MQTT code: https://github.com/Koenkk/zigbee2mqtt/issues/15124

MiguelAngelLV commented 1 year ago

I have one too.

I try create a quirks but only get a show a useless switch. The switch stage change but it don't do nothing.

pazzotranquillo commented 1 year ago

Have you guys found a custom solution for this? It would really great if this valve could be added to ZHA.

Margriko commented 1 year ago

I tried to put the pieces together for a custom quirk but was not successful. The quirk was applied successfully, switch status is shown in the UI, it reacts to the physical button press, but it is not possible to control it from the UI. Zigbee2MQTT implementation had to send some custom command to activate it, but I am a complete novice with ZHA quirks and Zigbee itself.

Anyway, if anyone wants to continue, here is the starting point for a quirk. Battery status is not shown correctly as it has 3 states instead of percentage and it needs to be implemented. Also I listed all data points I found in the logs.

from typing import Dict

from zigpy.profiles import zha
from zigpy.quirks import CustomDevice
import zigpy.types as t
from zigpy.zcl import foundation
from zigpy.zcl.clusters.general import Basic, Groups, Identify, OnOff, Ota, Scenes, Time
from zigpy.zcl.clusters.smartenergy import Metering

from zhaquirks import DoublingPowerConfigurationCluster
from zhaquirks.const import (
    DEVICE_TYPE,
    ENDPOINTS,
    INPUT_CLUSTERS,
    MODELS_INFO,
    OUTPUT_CLUSTERS,
    PROFILE_ID,
)
from zhaquirks.tuya import (
    TUYA_SET_DATA,
    TuyaLocalCluster,
    TuyaNewManufCluster,
    TuyaZBExternalSwitchTypeCluster,
    TuyaPowerConfigurationCluster2AA,
)
from zhaquirks.tuya.mcu import (
    DPToAttributeMapping,
    EnchantedDevice,
    TuyaMCUCluster,
    TuyaOnOff,
    TuyaOnOffNM,
    TuyaPowerConfigurationCluster,
)

class TuyaFJKZYellowValveManufCluster(TuyaMCUCluster):
    """Manufacturer Specific Cluster for the _TZ3210_0jxeoadc water valve sold as RAIN SEER FJKZ005C-Y."""

    # class BatteryState(t.enum8):
    #     """Battery state option."""

    #     Low = 0x00
    #     Medium = 0x01
    #     High = 0x02

    attributes = TuyaMCUCluster.attributes.copy()

    dp_to_attribute: Dict[int, DPToAttributeMapping] = {
        101: DPToAttributeMapping(
            TuyaOnOff.ep_attribute,
            "on_off",
        ),
        102: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "dp_2",
        ),
        103: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "dp_3",
        ),
        105: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "dp_5",
        ),
        106: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "dp_6",
        ),
        109: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "dp_9",
        ),
        110: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "dp_110",
        ),
        111: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "dp_111",
        ),
        115: DPToAttributeMapping(
            TuyaPowerConfigurationCluster.ep_attribute,
            "battery_status",
        ),
    }

    data_point_handlers = {
        101: "_dp_2_attr_update",
        102: "_dp_2_attr_update",
        103: "_dp_2_attr_update",
        105: "_dp_2_attr_update",
        106: "_dp_2_attr_update",
        109: "_dp_2_attr_update",
        110: "_dp_2_attr_update",
        111: "_dp_2_attr_update",
        115: "_dp_2_attr_update",
    }

class TuyaFJKZYellowValve(EnchantedDevice):
    signature = {
        MODELS_INFO: [("_TZ3210_0jxeoadc", "TS0049")],
        ENDPOINTS: {
            # <SimpleDescriptor endpoint=1 profile=260 device_type=266
            # 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,
                    TuyaFJKZYellowValveManufCluster.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,
                    TuyaOnOff,
                    TuyaPowerConfigurationCluster,
                    TuyaFJKZYellowValveManufCluster,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
        },
    }
igalg99 commented 11 months ago

Thanks @Margriko, I have tried it and it is working as you said. cannot control the switch from home assistant but can see the status changing when I physically press the button.

For the time I use a work around and combine it with a finger bot. the finger bot presses and status changes in home assistant. not ideal but it works for now.

Hope someone will manage to totally fix it and make it work properly so I can use the fingerbot elsewhere.

martinw72 commented 11 months ago

log output:

"data": { "ieee": "REDACTED", "nwk": 64536, "manufacturer": "_TZ3210_0jxeoadc", "model": "TS0049", "name": "_TZ3210_0jxeoadc TS0049", "quirk_applied": false, "quirk_class": "zigpy.device.Device", "manufacturer_code": 4417, "power_source": "Battery or Unknown", "lqi": 180, "rssi": -55, "last_seen": "2023-07-16T22:01:13", "available": false, "device_type": "EndDevice", "signature": { "node_descriptor": "NodeDescriptor(logical_type=<LogicalType.EndDevice: 2>, complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=<FrequencyBand.Freq2400MHz: 8>, mac_capability_flags=<MACCapabilityFlags.AllocateAddress: 128>, manufacturer_code=4417, maximum_buffer_size=66, maximum_incoming_transfer_size=66, server_mask=10752, maximum_outgoing_transfer_size=66, descriptor_capability_field=<DescriptorCapability.NONE: 0>, 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": "0x0000", "input_clusters": [ "0x0000", "0xef00" ], "output_clusters": [ "0x000a", "0x0019" ] } }, "manufacturer": "_TZ3210_0jxeoadc", "model": "TS0049" }, "active_coordinator": false, "entities": [], "neighbors": [], "routes": [], "endpoint_names": [ { "name": "ON_OFF_SWITCH" } ], "user_given_name": "Glasshausbew\u00e4sserung", "device_reg_id": "5d5ba450476da71385ab19e332022105", "area_id": "glashaus", "cluster_details": { "1": { "device_type": { "name": "ON_OFF_SWITCH", "id": 0 }, "profile_id": 260, "in_clusters": { "0xef00": { "endpoint_attribute": null, "attributes": {}, "unsupported_attributes": {} }, "0x0000": { "endpoint_attribute": "basic", "attributes": { "0x0004": { "attribute_name": "manufacturer", "value": "_TZ3210_0jxeoadc" }, "0x0005": { "attribute_name": "model", "value": "TS0049" } }, "unsupported_attributes": {} } }, "out_clusters": { "0x0019": { "endpoint_attribute": "ota", "attributes": {}, "unsupported_attributes": {} }, "0x000a": { "endpoint_attribute": "time", "attributes": {}, "unsupported_attributes": {} } } } } }

coutadeurf commented 11 months ago

Hi,

They had the same issue in Z2M, and this was because of that: "However, the main point that they found was that the valve wouldn't work until the sendDataPoint...() function was called with cmd = 0x04 ("Send commands")". After adding that value, it worked. I am not fluent enough in quirks to propose something but that may help someone to update the proposed solution

zenalien commented 11 months ago

hi everyone, any idea, if this will be ever available please ?

pnneil commented 11 months ago

FWIW I would like to add my voice to this request :)

coutadeurf commented 11 months ago

I have a test to do but as I am not close to the device this week, so I cannot test it myself now. If you want, you can do it: in the given quirks, replace TuyaOnOff by TuyaOnOffNM in the 2 places where it appears: I found a generic tuya valve quirks that is using TuyaOnOffNM. If it doesn't work, other option would be to understand what the quirk I found is doing differently but it isnquite complex...

pnneil commented 11 months ago

Thanks, but the behaviour is the same as described above for me...

coutadeurf commented 11 months ago

I finally decided to go with Z2M because I have another device that is not supported, I was just at the beginning of my setup and estimated that I will spend less time setting up Z2M than trying to blindly fix the quirks.

pnneil commented 11 months ago

I finally decided to go with Z2M because I have another device that is not supported, I was just at the beginning of my setup and estimated that I will spend less time setting up Z2M than trying to blindly fix the quirks.

I can understand that, for me this is the only device out of quite a few which is not yet supported, so I'm sticking with ZHA for the time being. Good luck!

pnneil commented 11 months ago

So I found this over at Z2MQTT, which is the converter for the device. I can see that a special conversion has been set up for the Switch and Timer for the valve, but I have no idea how to convert this into the required code for a quirk.

Any ideas?

const fz = require('zigbee-herdsman-converters/converters/fromZigbee'); const tz = require('zigbee-herdsman-converters/converters/toZigbee'); const exposes = require('zigbee-herdsman-converters/lib/exposes'); const reporting = require('zigbee-herdsman-converters/lib/reporting'); const extend = require('zigbee-herdsman-converters/lib/extend'); const e = exposes.presets; const ea = exposes.access; const tuya = require("zigbee-herdsman-converters/lib/tuya");

const TS0049_ValueConverter = { Switch:{ to: async (value, meta) => { const entity = meta.device.endpoints[0]; await tuya.sendDataPointBool(entity, 101, value.toUpperCase() === 'ON', 0x04); }, from: (value) => { return {false: 'OFF', true: 'ON'}[value]; }, }, IrrigationTimer:{ to: async (value, meta) => { const entity = meta.device.endpoints[0]; await tuya.sendDataPointValue(entity, 111, value, 0x04); }, from: (value) => { return value; }, }, }

const definition = { fingerprint: tuya.fingerprint('TS0049', ['_TZ3210_0jxeoadc']), model: 'TS0049', vendor: 'TuYa', description: 'Water Valve', fromZigbee: [tuya.fz.datapoints], toZigbee: [tuya.tz.datapoints], onEvent: tuya.onEventSetLocalTime, configure: tuya.configureMagicPacket, exposes: [ tuya.exposes.errorStatus(), tuya.exposes.switch(), tuya.exposes.countdown().withValueMin(0).withValueMax(255).withUnit('minutes') .withDescription('Max on time in minutes'), tuya.exposes.batteryState(), ], meta: { tuyaDatapoints: [ [26, 'error_status', tuya.valueConverter.raw], [101, 'state', TS0049_ValueConverter.Switch], [111, 'countdown', TS0049_ValueConverter.IrrigationTimer], [115, 'battery_state', tuya.valueConverter.batteryState], ], }, }; module.exports = definition;

zenalien commented 11 months ago

I finally decided to go with Z2M because I have another device that is not supported, I was just at the beginning of my setup and estimated that I will spend less time setting up Z2M than trying to blindly fix the quirks.

Is the device fully functionnal with Z2M ? By chance wou:ld you know what entities are there ?

Thank you .

coutadeurf commented 11 months ago

Hi,

Yes, the device is working fine in Z2M. Here are the entities that has been added to HA:

https://github.com/zigpy/zha-device-handlers/assets/40169798/59c8f464-d437-434d-b888-bc12bb946bae Regards

pnneil commented 11 months ago

So with some trial and error, (mostly the latter :) ) and borrowing other bits of code I found a way to control the valve. It is throwing warning messages which I haven't eliminated yet but it is a start...



from typing import Any, Dict, Optional, Union

from zigpy.profiles import zha
from zigpy.quirks import CustomDevice
import zigpy.types as t
from zigpy.zcl import foundation
from zigpy.zcl.clusters.general import Basic, Identify, OnOff, Ota, Time
from zigpy.zcl.clusters.smartenergy import Metering

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,
    TuyaPowerConfigurationCluster,
)

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

    attributes = TuyaMCUCluster.attributes.copy()

    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] = {
        26: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "error_status",
        ),
        101: DPToAttributeMapping(
            TuyaOnOff.ep_attribute,
            "on_off",
        ),
        110: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "dp_110",
        ),        
        111: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "irrigation_time",
        ),
        115: DPToAttributeMapping(
            TuyaPowerConfigurationCluster.ep_attribute,
            "battery_state",
        ),
    }

    data_point_handlers = {
        26: "_dp_2_attr_update",
        101: "_dp_2_attr_update",
        110: "_dp_2_attr_update",
        111: "_dp_2_attr_update",
        115: "_dp_2_attr_update",
    }

class TuyaIrrigationValve(EnchantedDevice):
    """Tuya green irrigation valve device."""
    signature = {
        MODELS_INFO: [("_TZ3210_0jxeoadc", "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,
                    TuyaValveFamilyCluster.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,
                    TuyaOnOff,
                    TuyaPowerConfigurationCluster,
                    TuyaValveFamilyCluster
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
        },
    }
danpeig commented 11 months ago

I will try it out If I have some time during the weekend.... what sort of warning messages do you get?

pnneil commented 11 months ago

I get these: 2023-08-08 12:42:14.504 WARNING (MainThread) [zigpy.zcl] [0x739A:1:0xef00] Unknown cluster command 5 b'\x00\x8ee\x01\x00\x01\x01' 2023-08-08 12:42:14.614 WARNING (MainThread) [zigpy.zcl] [0x739A:1:0xef00] Unknown cluster command 5 b'\x00\x8fn\x00\x00\x02\x00\x01'

This is the pair I get when I turn the valve on. I get another pair when I turn the valve off which are the same except for the last digit which is a zero. From what I can gather, the warning seems to be triggered by a response coming back from the valve but I could be wrong :) Here are some logs... The device id is 0x739A. home-assistant_zha_2023-08-08T07-03-45.684Z.log

danpeig commented 11 months ago

Hi,

I did some minor changes to expose the _irrigationtimer in the cluster settings in ZHA. This way you can set a different maximum duration for the cycle directly from HomeAssistant.

Still trying to understand the warnings and the override of the command function... I am also curious about how to read the battery status properly....


from typing import Any, Dict, Optional, Union

from zigpy.profiles import zha
from zigpy.quirks import CustomDevice
import zigpy.types as t
from zigpy.zcl import foundation
from zigpy.zcl.clusters.general import Basic, Identify, OnOff, Ota, Time
from zigpy.zcl.clusters.smartenergy import Metering

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,
    TuyaPowerConfigurationCluster,
)

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

    attributes = TuyaMCUCluster.attributes.copy()
    attributes.update(
        {
            0xEF01: ("irrigation_time", t.uint32_t, True),
            0xEF02: ("dp_110", t.uint32_t, True),
            0xEF03: ("error_status", 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] = {
        26: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "error_status",
        ),
        101: DPToAttributeMapping(
            TuyaOnOff.ep_attribute,
            "on_off",
        ),
        110: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "dp_110",
        ),        
        111: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "irrigation_time",
        ),
        115: DPToAttributeMapping(
            TuyaPowerConfigurationCluster.ep_attribute,
            "battery_percentage_remaining",
        ),
    }

    data_point_handlers = {
        26: "_dp_2_attr_update",
        101: "_dp_2_attr_update",
        110: "_dp_2_attr_update",
        111: "_dp_2_attr_update",
        115: "_dp_2_attr_update",
    }

class TuyaIrrigationValve(EnchantedDevice):
    """Tuya green irrigation valve device."""
    signature = {
        MODELS_INFO: [("_TZ3210_0jxeoadc", "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,
                    TuyaValveFamilyCluster.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,
                    TuyaOnOff,
                    TuyaPowerConfigurationCluster,
                    TuyaValveFamilyCluster
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
        },
    }
pnneil commented 11 months ago

Hi danpeig, After reloading, the new quirk, I didn't see any difference but after deleting the device and re-adding it I am getting a Battery reading, although it is only 2% (seems very low). I haven't seen any additional Entities exposed regarding the Irrigation timer, or is this meant to be controlled another way? The switch is definitely still working :) Thanks for your help!

MiguelAngelLV commented 11 months ago

The battery value is not a percentage. it have 3 values:

Low = 0x00 Medium = 0x01 High = 0x02

So, is necessary a custom battery converter.

pnneil commented 11 months ago

The battery value is not a percentage. it have 3 values:

Low = 0x00 Medium = 0x01 High = 0x02

So, is necessary a custom battery converter.

Thanks, That explains why the battery values I was getting were 1 or 2.

danpeig commented 11 months ago

Conversion from 0, 1, 2 to %:


115: DPToAttributeMapping(
            TuyaPowerConfigurationCluster.ep_attribute,
            "battery_percentage_remaining",
            converter=lambda x: x * 50,  # Adjust percen
tage
        ),
MiguelAngelLV commented 11 months ago

I would use (x+1)*33

HIGH = 99 MEDIUM = 66 LOW = 33

115: DPToAttributeMapping(
            TuyaPowerConfigurationCluster.ep_attribute,
            "battery_percentage_remaining",
            converter=lambda x: (x+1) * 33,  # Adjust percen
tage
        ),
MiguelAngelLV commented 11 months ago

A little improvement battery

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, Ota, Time, PowerConfiguration

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: ("irrigation_time", t.uint32_t, True),
            0xEF02: ("dp_110", t.uint32_t, True),
            0xEF03: ("error_status", 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] = {
        26: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "error_status",
        ),
        101: DPToAttributeMapping(
            TuyaOnOff.ep_attribute,
            "on_off",
        ),
        110: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "dp_110",
        ),
        111: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "irrigation_time",
        ),
        115: DPToAttributeMapping(
            TuyaValveFamilyBattery.ep_attribute,
            "battery_percentage_remaining",
        ),
    }

    data_point_handlers = {
        26: "_dp_2_attr_update",
        101: "_dp_2_attr_update",
        110: "_dp_2_attr_update",
        111: "_dp_2_attr_update",
        115: "_dp_2_attr_update",
    }

class TuyaIrrigationValve(EnchantedDevice):
    """Tuya green irrigation valve device."""
    signature = {
        MODELS_INFO: [("_TZ3210_0jxeoadc", "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,
                    TuyaValveFamilyCluster.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,
                    TuyaOnOff,
                    TuyaValveFamilyBattery,
                    TuyaValveFamilyCluster
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
        },
    }

I added a rule for battery (low => 10%, medium => 50%, high => 90%), they can be modified easily.

And added the battery size and quantity too.

pnneil commented 11 months ago

Thanks MiguelAngelLV. its working well!

pazzotranquillo commented 11 months ago

you guys are amazing! Many thanks!

MiguelAngelLV commented 10 months ago

I check that irrigation timer works, but don't show the input, you need go to configuration, open the clusters and set the value.

Any way to get a input in device config?

813648 commented 10 months ago

Está a funcionar 5. Muito Obrigado!!! - It's working 5. Thank you very much!!! 2023-08-30_223808

raddacle commented 9 months ago

The quirk works great! Though I'm having an issue where it automatically turns off after a minute every time. I believe this is a default setting that you could disable through the tuya app, but how can I disable it through ZHA?

EDIT: Nevermind, found it. For anyone who runs into this too, go to Manage Zigbee Device, select cluster TuyaValveFamilyCluster, and then select attribute irrigation_time. From there you can set any maximum minutes that it will be on; and there's no manufacturer code needed.

Matze89x commented 8 months ago

When will this be officially adopted? I'm currently still using the custom version in Home Assistant. Thank you very much.

MiguelAngelLV commented 8 months ago

When will this be officially adopted? I'm currently still using the custom version in Home Assistant. Thank you very much.

The quirk works, but, it is not finished... I think that It needs "a little of love" before merge in official.

guciopl commented 7 months ago

Está a funcionar 5. Muito Obrigado!!! - It's working 5. Thank you very much!!! 2023-08-30_223808

I have same Tuya valve but green and it needs RF433 gateway as it cannot connect to wifi on its own. Does this yellow require gateway as well or it is different model? I cant get my green one to work with HA at all.

coutadeurf commented 7 months ago

Hi,

Yellow is zigbee powered so you need a zigbee adapter to make it work.

MiguelAngelLV commented 7 months ago

@guciopl there are a lot os versions. We are talking about zigbee version (powered with AC or battery, but, zigbee).

If the yours use RF433, it is not valid version with ZHA.

moryoav commented 6 months ago

The quirk works great! Though I'm having an issue where it automatically turns off after a minute every time. I believe this is a default setting that you could disable through the tuya app, but how can I disable it through ZHA?

EDIT: Nevermind, found it. For anyone who runs into this too, go to Manage Zigbee Device, select cluster TuyaValveFamilyCluster, and then select attribute irrigation_time. From there you can set any maximum minutes that it will be on; and there's no manufacturer code needed.

It's very limiting that the irrigation time cannot be controlled directly like any other device control. Can it be added?

rdeva31 commented 4 months ago

@MiguelAngelLV whats needs to be done in order for this be turned into a PR? I'm happy to take this forward if you tell me where the gaps are.

Is it just fixing up CI issues in https://github.com/zigpy/zha-device-handlers/pull/2624? cc @danpeig too since you own the PR

danpeig commented 4 months ago

It was failing to pass verification checks. I have no proficiency with the python to fix the errors.

MiguelAngelLV commented 3 months ago

Any one have problem with battery level? After 4 months show 90% yet and I don't think could be possible...

sada1410 commented 3 months ago

Hello,

I used the Quirk and it worked great. However, after a while my 4 valves stopped working.

I can still read but can no longer write. I can also see the battery level. When I reconfigure the valves, I get the message that TuyaOnOff cannot be bound.

What could be the reason for this? Can anyone help me?

2024-04-03 09_32_25-Window

sada1410 commented 2 months ago

If I try to reconfigure the device the following error log appears:

2024-04-13 14:55:45.946 DEBUG (MainThread) [homeassistant.components.zha.core.cluster_handlers] [0x2D11:1:0x0006]: Failed to bind 'on_off' cluster: Failed to deliver message: <EmberStatus.DELIVERY_FAILED: 102> Traceback (most recent call last): File "/usr/src/homeassistant/homeassistant/components/zha/core/cluster_handlers/__init__.py", line 203, in bind res = await self.cluster.bind() ^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.12/site-packages/zhaquirks/tuya/__init__.py", line 572, in bind await self.spell_attribute_reads() File "/usr/local/lib/python3.12/site-packages/zhaquirks/tuya/__init__.py", line 586, in spell_attribute_reads await basic_cluster.read_attributes(attr_to_read) File "/usr/local/lib/python3.12/site-packages/zigpy/zcl/__init__.py", line 528, in read_attributes result = await self.read_attributes_raw(to_read, manufacturer=manufacturer) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.12/site-packages/zigpy/zcl/__init__.py", line 377, in request return await self._endpoint.request( ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.12/site-packages/zigpy/endpoint.py", line 253, in request return await self.device.request( ^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.12/site-packages/zigpy/device.py", line 339, in request await send_request() File "/usr/local/lib/python3.12/site-packages/zigpy/application.py", line 841, in request await self.send_packet( File "/usr/local/lib/python3.12/site-packages/bellows/zigbee/application.py", line 931, in send_packet raise zigpy.exceptions.DeliveryError( zigpy.exceptions.DeliveryError: Failed to deliver message: <EmberStatus.DELIVERY_FAILED: 102>

It is possible to read and write the irrigation value. Only TuyaOnOff doesn't work.

sada1410 commented 2 months ago

I tried to connect the valve to an original tuya Zigbee gateway. The valve is working. So maybe something is missing in the communication and than the valve is unavailable. How can I debug the messages and how to check if commands are missing? Anyone has some ideas? I tried a lot of things in the quirck but nothing helped. My gain is to have a working and reliable quirk for this valve.

narthollis commented 1 month ago

I would like to quickly add that I have just used the custom profile posted by MiguelAngelLV on Aug 9, 2023 and that it just worked for me. Many thanks.

alexdaszek commented 1 month ago

The quirk works great! Though I'm having an issue where it automatically turns off after a minute every time. I believe this is a default setting that you could disable through the tuya app, but how can I disable it through ZHA?

EDIT: Nevermind, found it. For anyone who runs into this too, go to Manage Zigbee Device, select cluster TuyaValveFamilyCluster, and then select attribute irrigation_time. From there you can set any maximum minutes that it will be on; and there's no manufacturer code needed.

Did this end up actually working for you? I do the same thing, but the value never sticks and always goes back to 1 after writing it.

image

I'm using this for drip irrigation, ideally I want HA to turn it on and off, but because it works this way I want a 4 hour timer, so I set it to 240 minutes and write, and read back and it says 240. But then if I check it again the next day it's back to a value of 1 minute. I checked the python code of the quirk, doesn't seem to be stored in there to change manually so not sure where this value lives if there's other options for changing it. I'm using ZHA with a Sonoff Zigbee dongle. Anything else I can try?

moryoav commented 1 month ago

This works for me fine. Only if I re-pair the device with the zigbee router it resets to default, but in day-to-day operations this works fine. If you can't manage then you can always create an automation that'll run daily and put that value again automatically. Check out the service "zha.set_zigbee_cluster_attribute"

On Wed, May 29, 2024, 00:02 Alex Daszek @.***> wrote:

The quirk works great! Though I'm having an issue where it automatically turns off after a minute every time. I believe this is a default setting that you could disable through the tuya app, but how can I disable it through ZHA?

EDIT: Nevermind, found it. For anyone who runs into this too, go to Manage Zigbee Device, select cluster TuyaValveFamilyCluster, and then select attribute irrigation_time. From there you can set any maximum minutes that it will be on; and there's no manufacturer code needed.

Did this end up actually working for you? I do the same thing, but the value never sticks and always goes back to 1 after writing it.

image.png (view on web) https://github.com/zigpy/zha-device-handlers/assets/8670681/caeee673-6a17-44bf-8cb9-5ace18f715a7

I'm using this for drip irrigation, ideally I want HA to turn it on and off, but because it works this way I want a 4 hour time, so I set it to 240 minutes and write, and read back and it says 240. But then if I check it again the next day it's back to a value of 1 minute. I checked the python code of the quirk, doesn't seem to be stored in there to change manually so not sure where this value lives if there's other options for changing it. Anything else I can try?

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

alexdaszek commented 1 month ago

This works for me fine. Only if I re-pair the device with the zigbee router it resets to default, but in day-to-day operations this works fine. If you can't manage then you can always create an automation that'll run daily and put that value again automatically. Check out the service "zha.set_zigbee_cluster_attribute" On Wed, May 29, 2024, 00:02 Alex Daszek @.> wrote: The quirk works great! Though I'm having an issue where it automatically turns off after a minute every time. I believe this is a default setting that you could disable through the tuya app, but how can I disable it through ZHA? EDIT: Nevermind, found it. For anyone who runs into this too, go to Manage Zigbee Device, select cluster TuyaValveFamilyCluster, and then select attribute irrigation_time. From there you can set any maximum minutes that it will be on; and there's no manufacturer code needed. Did this end up actually working for you? I do the same thing, but the value never sticks and always goes back to 1 after writing it. image.png (view on web) https://github.com/zigpy/zha-device-handlers/assets/8670681/caeee673-6a17-44bf-8cb9-5ace18f715a7 I'm using this for drip irrigation, ideally I want HA to turn it on and off, but because it works this way I want a 4 hour time, so I set it to 240 minutes and write, and read back and it says 240. But then if I check it again the next day it's back to a value of 1 minute. I checked the python code of the quirk, doesn't seem to be stored in there to change manually so not sure where this value lives if there's other options for changing it. Anything else I can try? — Reply to this email directly, view it on GitHub <#2377 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAN25E6ZGK34T744LJWAM23ZETWFDAVCNFSM6AAAAAAXVTZIECVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDCMZWGA4TONRVGY . You are receiving this because you commented.Message ID: @.>

I think I got that working, thank you. I'm still having issues with the device itself, it doesn't do anything when turning on or off. Originally it was working OK, there was a long lag before turning it on in HA and it actually turning on and now I can't get it to work at all. But that's another issue...

Here's what worked for me as far as having an automation that writes the irrigation_time value before turning the switch on:

image

The delay here is probably irrelevant, since the device turns itself off once irrigation_time completes.

image

image

I got the ieee value from ZHA, and all of the other data from the "manage device" menu I shared an image of in my original comment. It gives you the attribute and cluster IDs as hex, but the service needs them as an int so you have to convert them.

Just an interesting side note that may or may not be relevant, is the debug node reports from the service that entity_id is undefined but it isn't one of the values listed by the service so not sure what's going on there. I assume it's optional, it has the ieee value after all.

image image

moryoav commented 1 month ago

I recently discovered another issue with zha and this valve, sometimes sending the command to turn it on doesn't turn it on. It shows as if it's on but it's not. Physically pressing the button on the valve turns it on if that happens, but this is not a good solution obviously. So what I realized was that if I send the command to turn it on and then off, repeat that a few times, then after a few times it successfully wakes up the device and turning it on succeeds. Anyone else seeing this behavior? I don't know how to "verify" it's really on, I've been seen a zigbee device that's reporting to be on when it's not (I have a few of these valves and I'm seeing this behavior in all of them, so this is not a unique device failure)