Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Device Support Request] Tuya ultrasonic level sensor (TS0601 by _TZE284_kyyu8rbj) #3356

Closed
hickey opened this issue Sep 10, 2024 · 13 comments · Fixed by #3744
Closed

[Device Support Request] Tuya ultrasonic level sensor (TS0601 by _TZE284_kyyu8rbj) #3356

hickey opened this issue Sep 10, 2024 · 13 comments · Fixed by #3744
Labels
Tuya Request/PR regarding a Tuya device

Comments

@hickey
Copy link

hickey commented Sep 10, 2024

Problem description

Recently bought the Tuya ultrasonic liquid level sensor (https://www.aliexpress.us/item/3256807266442468.html?spm=a2g0o.order_list.order_list_main.11.21ef1802lD5999&gatewayAdapt=glo2usa) and while it will pair with ZHA, it only reports LQI and RSSI.

I am very new (i.e. just started reading about ZHA quirks) and would like to assist/work on this quirk. Mostly I will probably need some guidance and basic information for building the Python script for the quirk. Part of the reason I would like to work on this quirk is to better understand the Zigbee protocols, add to the volunteers contributing quirks and write a quirk for the Tuya water quality sensor I also got (this has many more values to read and will be much more interesting project).

I am making the assumption from the diagnostic output below that the reporting_status is the attribute that contains the level value. So it seems that this should be a pretty straight forward quirk to implement.

Solution description

I am envisioning a custom quirk that reports back a single value to ZHA which is the last level measured.

Screenshots/Video

Screenshots/Video

[Paste/upload your media here]

Device signature

Device 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": "0x0051",
      "input_clusters": [
        "0x0000",
        "0x0004",
        "0x0005",
        "0xed00",
        "0xef00"
      ],
      "output_clusters": [
        "0x000a",
        "0x0019"
      ]
    }
  },
  "manufacturer": "_TZE284_kyyu8rbj",
  "model": "TS0601",
  "class": "zigpy.device.Device"
}

Diagnostic information

Diagnostic information
{
  "home_assistant": {
    "installation_type": "Home Assistant OS",
    "version": "2024.9.1",
    "dev": false,
    "hassio": true,
    "virtualenv": false,
    "python_version": "3.12.4",
    "docker": true,
    "arch": "x86_64",
    "timezone": "America/New_York",
    "os_name": "Linux",
    "os_version": "6.6.46-haos",
    "supervisor": "2024.08.0",
    "host_os": "Home Assistant OS 13.1",
    "docker_version": "26.1.4",
    "chassis": "vm",
    "run_as_root": true
  },
  "custom_components": {
    "adaptive_lighting": {
      "documentation": "https://github.com/basnijholt/adaptive-lighting#readme",
      "version": "1.22.0",
      "requirements": [
        "ulid-transform"
      ]
    },
    "astroweather": {
      "documentation": "https://github.com/mawinkler/astroweather",
      "version": "0.50.4",
      "requirements": [
        "pyastroweatherio==0.50.4"
      ]
    },
    "pirateweather": {
      "documentation": "https://github.com/alexander0042/pirate-weather-ha",
      "version": "1.5.2",
      "requirements": [
        "python-forecastio==1.4.0"
      ]
    },
    "proxmoxve": {
      "documentation": "https://github.com/dougiteixeira/proxmoxve",
      "version": "3.4.4",
      "requirements": [
        "proxmoxer==2.0.1"
      ]
    },
    "ham_radio_propagation": {
      "documentation": "https://github.com/emics/ham_radio_propagation",
      "version": "1.1.6",
      "requirements": [
        "xmltodict==0.13.0"
      ]
    },
    "nws_alerts": {
      "documentation": "https://github.com/finity69x2/nws_alerts/",
      "version": "5.0",
      "requirements": []
    },
    "hacs": {
      "documentation": "https://hacs.xyz/docs/configuration/start",
      "version": "1.34.0",
      "requirements": [
        "aiogithubapi>=22.10.1"
      ]
    },
    "blitzortung": {
      "documentation": "https://github.com/mrk-its/homeassistant-blitzortung",
      "version": "1.0.1",
      "requirements": [
        "paho-mqtt>=1.5.0"
      ]
    },
    "alexa_media": {
      "documentation": "https://github.com/alandtse/alexa_media_player/wiki",
      "version": "4.12.7",
      "requirements": [
        "alexapy==1.28.2",
        "packaging>=20.3",
        "wrapt>=1.14.0"
      ]
    },
    "sengledapi": {
      "documentation": "https://github.com/jfarmer08/ha-sengledapi",
      "version": "0.2",
      "requirements": [
        "paho-mqtt>=1.6.1"
      ]
    }
  },
  "integration_manifest": {
    "domain": "zha",
    "name": "Zigbee Home Automation",
    "after_dependencies": [
      "onboarding",
      "usb"
    ],
    "codeowners": [
      "dmulcahey",
      "adminiuga",
      "puddly",
      "TheJulianJES"
    ],
    "config_flow": true,
    "dependencies": [
      "file_upload"
    ],
    "documentation": "https://www.home-assistant.io/integrations/zha",
    "iot_class": "local_polling",
    "loggers": [
      "aiosqlite",
      "bellows",
      "crccheck",
      "pure_pcapy3",
      "zhaquirks",
      "zigpy",
      "zigpy_deconz",
      "zigpy_xbee",
      "zigpy_zigate",
      "zigpy_znp",
      "zha",
      "universal_silabs_flasher"
    ],
    "requirements": [
      "universal-silabs-flasher==0.0.22",
      "zha==0.0.32"
    ],
    "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": 6.535099998927762e-05
    },
    "d6f2996f0c032531fe2c75086033e663": {
      "wait_import_platforms": -0.20687171500003387,
      "wait_base_component": -0.0009626230000208125,
      "config_entry_setup": 20.54208517500001
    }
  },
  "data": {
    "ieee": "**REDACTED**",
    "nwk": 16803,
    "manufacturer": "_TZE284_kyyu8rbj",
    "model": "TS0601",
    "name": "_TZE284_kyyu8rbj TS0601",
    "quirk_applied": false,
    "quirk_class": "zigpy.device.Device",
    "quirk_id": null,
    "manufacturer_code": 4417,
    "power_source": "Battery or Unknown",
    "lqi": 188,
    "rssi": -53,
    "last_seen": "2024-09-10T10:45:48",
    "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": "0x0051",
          "input_clusters": [
            "0x0000",
            "0x0004",
            "0x0005",
            "0xed00",
            "0xef00"
          ],
          "output_clusters": [
            "0x000a",
            "0x0019"
          ]
        }
      },
      "manufacturer": "_TZE284_kyyu8rbj",
      "model": "TS0601"
    },
    "active_coordinator": false,
    "entities": [
      {
        "entity_id": "sensor.tze284_kyyu8rbj_ts0601_rssi",
        "name": "_TZE284_kyyu8rbj TS0601"
      },
      {
        "entity_id": "sensor.tze284_kyyu8rbj_ts0601_lqi",
        "name": "_TZE284_kyyu8rbj TS0601"
      },
      {
        "entity_id": "update.tze284_kyyu8rbj_ts0601_firmware",
        "name": "_TZE284_kyyu8rbj TS0601"
      }
    ],
    "neighbors": [],
    "routes": [],
    "endpoint_names": [
      {
        "name": "SMART_PLUG"
      }
    ],
    "user_given_name": null,
    "device_reg_id": "8c08ec4f3c81c9f5ca290039080b25b9",
    "area_id": null,
    "cluster_details": {
      "1": {
        "device_type": {
          "name": "SMART_PLUG",
          "id": 81
        },
        "profile_id": 260,
        "in_clusters": {
          "0x0004": {
            "endpoint_attribute": "groups",
            "attributes": {
              "0xfffd": {
                "attribute": "ZCLAttributeDef(id=0xFFFD, name='cluster_revision', type=<class 'zigpy.types.basic.uint16_t'>, zcl_type=<DataTypeId.uint16: 33>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0000": {
                "attribute": "ZCLAttributeDef(id=0x0000, name='name_support', type=<flag 'NameSupport'>, zcl_type=<DataTypeId.map8: 24>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
                "value": 0
              },
              "0xfffe": {
                "attribute": "ZCLAttributeDef(id=0xFFFE, name='reporting_status', type=<enum 'AttributeReportingStatus'>, zcl_type=<DataTypeId.enum8: 48>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              }
            },
            "unsupported_attributes": [
              65534,
              "reporting_status"
            ]
          },
          "0x0005": {
            "endpoint_attribute": "scenes",
            "attributes": {
              "0xfffd": {
                "attribute": "ZCLAttributeDef(id=0xFFFD, name='cluster_revision', type=<class 'zigpy.types.basic.uint16_t'>, zcl_type=<DataTypeId.uint16: 33>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0000": {
                "attribute": "ZCLAttributeDef(id=0x0000, name='count', type=<class 'zigpy.types.basic.uint8_t'>, zcl_type=<DataTypeId.uint8: 32>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0002": {
                "attribute": "ZCLAttributeDef(id=0x0002, name='current_group', type=<class 'zigpy.types.basic.uint16_t'>, zcl_type=<DataTypeId.uint16: 33>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0001": {
                "attribute": "ZCLAttributeDef(id=0x0001, name='current_scene', type=<class 'zigpy.types.basic.uint8_t'>, zcl_type=<DataTypeId.uint8: 32>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
                "value": 0
              },
              "0x0005": {
                "attribute": "ZCLAttributeDef(id=0x0005, name='last_configured_by', type=<class 'zigpy.types.named.EUI64'>, zcl_type=<DataTypeId.EUI64: 240>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0004": {
                "attribute": "ZCLAttributeDef(id=0x0004, name='name_support', type=<flag 'NameSupport'>, zcl_type=<DataTypeId.map8: 24>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
                "value": 0
              },
              "0xfffe": {
                "attribute": "ZCLAttributeDef(id=0xFFFE, name='reporting_status', type=<enum 'AttributeReportingStatus'>, zcl_type=<DataTypeId.enum8: 48>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0003": {
                "attribute": "ZCLAttributeDef(id=0x0003, name='scene_valid', type=<enum 'Bool'>, zcl_type=<DataTypeId.bool_: 16>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
                "value": null
              }
            },
            "unsupported_attributes": [
              65534,
              "reporting_status"
            ]
          },
          "0xef00": {
            "endpoint_attribute": null,
            "attributes": {},
            "unsupported_attributes": []
          },
          "0x0000": {
            "endpoint_attribute": "basic",
            "attributes": {
              "0x0013": {
                "attribute": "ZCLAttributeDef(id=0x0013, name='alarm_mask', type=<flag 'AlarmMask'>, zcl_type=<DataTypeId.map8: 24>, access=<ZCLAttributeAccess.Read|Write: 3>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0001": {
                "attribute": "ZCLAttributeDef(id=0x0001, name='app_version', type=<class 'zigpy.types.basic.uint8_t'>, zcl_type=<DataTypeId.uint8: 32>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": 77
              },
              "0xfffd": {
                "attribute": "ZCLAttributeDef(id=0xFFFD, name='cluster_revision', type=<class 'zigpy.types.basic.uint16_t'>, zcl_type=<DataTypeId.uint16: 33>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0006": {
                "attribute": "ZCLAttributeDef(id=0x0006, name='date_code', type=<class 'zigpy.types.basic.LimitedCharString.<locals>.LimitedCharString'>, zcl_type=<DataTypeId.string: 66>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": ""
              },
              "0x0012": {
                "attribute": "ZCLAttributeDef(id=0x0012, name='device_enabled', type=<enum 'Bool'>, zcl_type=<DataTypeId.bool_: 16>, access=<ZCLAttributeAccess.Read|Write: 3>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0014": {
                "attribute": "ZCLAttributeDef(id=0x0014, name='disable_local_config', type=<flag 'DisableLocalConfig'>, zcl_type=<DataTypeId.map8: 24>, access=<ZCLAttributeAccess.Read|Write: 3>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0008": {
                "attribute": "ZCLAttributeDef(id=0x0008, name='generic_device_class', type=<enum 'GenericDeviceClass'>, zcl_type=<DataTypeId.enum8: 48>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0009": {
                "attribute": "ZCLAttributeDef(id=0x0009, name='generic_device_type', type=<enum 'GenericLightingDeviceType'>, zcl_type=<DataTypeId.enum8: 48>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0003": {
                "attribute": "ZCLAttributeDef(id=0x0003, name='hw_version', type=<class 'zigpy.types.basic.uint8_t'>, zcl_type=<DataTypeId.uint8: 32>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": 1
              },
              "0x0010": {
                "attribute": "ZCLAttributeDef(id=0x0010, name='location_desc', type=<class 'zigpy.types.basic.LimitedCharString.<locals>.LimitedCharString'>, zcl_type=<DataTypeId.string: 66>, access=<ZCLAttributeAccess.Read|Write: 3>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0004": {
                "attribute": "ZCLAttributeDef(id=0x0004, name='manufacturer', type=<class 'zigpy.types.basic.LimitedCharString.<locals>.LimitedCharString'>, zcl_type=<DataTypeId.string: 66>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": "_TZE284_kyyu8rbj"
              },
              "0x000c": {
                "attribute": "ZCLAttributeDef(id=0x000C, name='manufacturer_version_details', type=<class 'zigpy.types.basic.CharacterString'>, zcl_type=<DataTypeId.string: 66>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0005": {
                "attribute": "ZCLAttributeDef(id=0x0005, name='model', type=<class 'zigpy.types.basic.LimitedCharString.<locals>.LimitedCharString'>, zcl_type=<DataTypeId.string: 66>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": "TS0601"
              },
              "0x0011": {
                "attribute": "ZCLAttributeDef(id=0x0011, name='physical_env', type=<enum 'PhysicalEnvironment'>, zcl_type=<DataTypeId.enum8: 48>, access=<ZCLAttributeAccess.Read|Write: 3>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0007": {
                "attribute": "ZCLAttributeDef(id=0x0007, name='power_source', type=<enum 'PowerSource'>, zcl_type=<DataTypeId.enum8: 48>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
                "value": 3
              },
              "0x000a": {
                "attribute": "ZCLAttributeDef(id=0x000A, name='product_code', type=<class 'zigpy.types.basic.LVBytes'>, zcl_type=<DataTypeId.octstr: 65>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x000e": {
                "attribute": "ZCLAttributeDef(id=0x000E, name='product_label', type=<class 'zigpy.types.basic.CharacterString'>, zcl_type=<DataTypeId.string: 66>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x000b": {
                "attribute": "ZCLAttributeDef(id=0x000B, name='product_url', type=<class 'zigpy.types.basic.CharacterString'>, zcl_type=<DataTypeId.string: 66>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0xfffe": {
                "attribute": "ZCLAttributeDef(id=0xFFFE, name='reporting_status', type=<enum 'AttributeReportingStatus'>, zcl_type=<DataTypeId.enum8: 48>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": 0
              },
              "0x000d": {
                "attribute": "ZCLAttributeDef(id=0x000D, name='serial_number', type=<class 'zigpy.types.basic.CharacterString'>, zcl_type=<DataTypeId.string: 66>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0002": {
                "attribute": "ZCLAttributeDef(id=0x0002, name='stack_version', type=<class 'zigpy.types.basic.uint8_t'>, zcl_type=<DataTypeId.uint8: 32>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": 0
              },
              "0x4000": {
                "attribute": "ZCLAttributeDef(id=0x4000, name='sw_build_id', type=<class 'zigpy.types.basic.CharacterString'>, zcl_type=<DataTypeId.string: 66>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0000": {
                "attribute": "ZCLAttributeDef(id=0x0000, name='zcl_version', type=<class 'zigpy.types.basic.uint8_t'>, zcl_type=<DataTypeId.uint8: 32>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
                "value": 3
              }
            },
            "unsupported_attributes": [
              8,
              9,
              10,
              11,
              12,
              13,
              14,
              17,
              18,
              19,
              16384,
              "alarm_mask",
              "device_enabled",
              "generic_device_class",
              "generic_device_type",
              "manufacturer_version_details",
              "physical_env",
              "product_code",
              "product_label",
              "product_url",
              "serial_number",
              "sw_build_id"
            ]
          },
          "0xed00": {
            "endpoint_attribute": null,
            "attributes": {},
            "unsupported_attributes": []
          }
        },
        "out_clusters": {
          "0x0019": {
            "endpoint_attribute": "ota",
            "attributes": {
              "0xfffd": {
                "attribute": "ZCLAttributeDef(id=0xFFFD, name='cluster_revision', type=<class 'zigpy.types.basic.uint16_t'>, zcl_type=<DataTypeId.uint16: 33>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0002": {
                "attribute": "ZCLAttributeDef(id=0x0002, name='current_file_version', type=<class 'zigpy.types.basic.uint32_t'>, zcl_type=<DataTypeId.uint32: 35>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": 77
              },
              "0x0003": {
                "attribute": "ZCLAttributeDef(id=0x0003, name='current_zigbee_stack_version', type=<class 'zigpy.types.basic.uint16_t'>, zcl_type=<DataTypeId.uint16: 33>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0004": {
                "attribute": "ZCLAttributeDef(id=0x0004, name='downloaded_file_version', type=<class 'zigpy.types.basic.uint32_t'>, zcl_type=<DataTypeId.uint32: 35>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0005": {
                "attribute": "ZCLAttributeDef(id=0x0005, name='downloaded_zigbee_stack_version', type=<class 'zigpy.types.basic.uint16_t'>, zcl_type=<DataTypeId.uint16: 33>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0001": {
                "attribute": "ZCLAttributeDef(id=0x0001, name='file_offset', type=<class 'zigpy.types.basic.uint32_t'>, zcl_type=<DataTypeId.uint32: 35>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x000a": {
                "attribute": "ZCLAttributeDef(id=0x000A, name='image_stamp', type=<class 'zigpy.types.basic.uint32_t'>, zcl_type=<DataTypeId.uint32: 35>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0008": {
                "attribute": "ZCLAttributeDef(id=0x0008, name='image_type_id', type=<class 'zigpy.types.basic.uint16_t'>, zcl_type=<DataTypeId.uint16: 33>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0006": {
                "attribute": "ZCLAttributeDef(id=0x0006, name='image_upgrade_status', type=<enum 'ImageUpgradeStatus'>, zcl_type=<DataTypeId.enum8: 48>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0007": {
                "attribute": "ZCLAttributeDef(id=0x0007, name='manufacturer_id', type=<class 'zigpy.types.basic.uint16_t'>, zcl_type=<DataTypeId.uint16: 33>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0009": {
                "attribute": "ZCLAttributeDef(id=0x0009, name='minimum_block_req_delay', type=<class 'zigpy.types.basic.uint16_t'>, zcl_type=<DataTypeId.uint16: 33>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0xfffe": {
                "attribute": "ZCLAttributeDef(id=0xFFFE, name='reporting_status', type=<enum 'AttributeReportingStatus'>, zcl_type=<DataTypeId.enum8: 48>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x000b": {
                "attribute": "ZCLAttributeDef(id=0x000B, name='upgrade_activation_policy', type=<enum 'UpgradeActivationPolicy'>, zcl_type=<DataTypeId.enum8: 48>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0000": {
                "attribute": "ZCLAttributeDef(id=0x0000, name='upgrade_server_id', type=<class 'zigpy.types.named.EUI64'>, zcl_type=<DataTypeId.EUI64: 240>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
                "value": null
              },
              "0x000c": {
                "attribute": "ZCLAttributeDef(id=0x000C, name='upgrade_timeout_policy', type=<enum 'UpgradeTimeoutPolicy'>, zcl_type=<DataTypeId.enum8: 48>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              }
            },
            "unsupported_attributes": []
          },
          "0x000a": {
            "endpoint_attribute": "time",
            "attributes": {
              "0xfffd": {
                "attribute": "ZCLAttributeDef(id=0xFFFD, name='cluster_revision', type=<class 'zigpy.types.basic.uint16_t'>, zcl_type=<DataTypeId.uint16: 33>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
                "value": 1
              },
              "0x0004": {
                "attribute": "ZCLAttributeDef(id=0x0004, name='dst_end', type=<class 'zigpy.types.basic.uint32_t'>, zcl_type=<DataTypeId.uint32: 35>, access=<ZCLAttributeAccess.Read|Write: 3>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0005": {
                "attribute": "ZCLAttributeDef(id=0x0005, name='dst_shift', type=<class 'zigpy.types.basic.int32s'>, zcl_type=<DataTypeId.int32: 43>, access=<ZCLAttributeAccess.Read|Write: 3>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0003": {
                "attribute": "ZCLAttributeDef(id=0x0003, name='dst_start', type=<class 'zigpy.types.basic.uint32_t'>, zcl_type=<DataTypeId.uint32: 35>, access=<ZCLAttributeAccess.Read|Write: 3>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0008": {
                "attribute": "ZCLAttributeDef(id=0x0008, name='last_set_time', type=<class 'zigpy.types.named.UTCTime'>, zcl_type=<DataTypeId.UTC: 226>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0007": {
                "attribute": "ZCLAttributeDef(id=0x0007, name='local_time', type=<class 'zigpy.types.named.LocalTime'>, zcl_type=<DataTypeId.uint32: 35>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0xfffe": {
                "attribute": "ZCLAttributeDef(id=0xFFFE, name='reporting_status', type=<enum 'AttributeReportingStatus'>, zcl_type=<DataTypeId.enum8: 48>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0006": {
                "attribute": "ZCLAttributeDef(id=0x0006, name='standard_time', type=<class 'zigpy.types.named.StandardTime'>, zcl_type=<DataTypeId.uint32: 35>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0000": {
                "attribute": "ZCLAttributeDef(id=0x0000, name='time', type=<class 'zigpy.types.named.UTCTime'>, zcl_type=<DataTypeId.UTC: 226>, access=<ZCLAttributeAccess.Read|Write_Optional: 5>, mandatory=True, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0001": {
                "attribute": "ZCLAttributeDef(id=0x0001, name='time_status', type=<flag 'TimeStatus'>, zcl_type=<DataTypeId.map8: 24>, access=<ZCLAttributeAccess.Read|Write_Optional: 5>, mandatory=True, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0002": {
                "attribute": "ZCLAttributeDef(id=0x0002, name='time_zone', type=<class 'zigpy.types.basic.int32s'>, zcl_type=<DataTypeId.int32: 43>, access=<ZCLAttributeAccess.Read|Write: 3>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              },
              "0x0009": {
                "attribute": "ZCLAttributeDef(id=0x0009, name='valid_until_time', type=<class 'zigpy.types.named.UTCTime'>, zcl_type=<DataTypeId.UTC: 226>, access=<ZCLAttributeAccess.Read|Write: 3>, mandatory=False, is_manufacturer_specific=False)",
                "value": null
              }
            },
            "unsupported_attributes": [
              1,
              2,
              6,
              7,
              "local_time",
              "standard_time",
              "time_status",
              "time_zone"
            ]
          }
        }
      }
    }
  }
}

Logs

Logs
[Paste the logs here]

Custom quirk

Custom quirk
[Paste your custom quirk here]

Additional information

No response

@brooksaw
Copy link

brooksaw commented Oct 5, 2024

@hickey - Any luck with this? Just got one and am trying to get it going and have made some progress but wouldn't mind some input

@brooksaw
Copy link

brooksaw commented Oct 5, 2024

{
"alarm": "normal",
"alert": "triggered",
"linkquality": 75,
"status": "off",
"water_level": -1124073472
}

This is what I have with state now

@brooksaw
Copy link

brooksaw commented Oct 7, 2024

{
"battery_percentage": 0,
"installation_height": null,
"linkquality": 114,
"liquid_level_percent": 0,
"max_set": null,
"mini_set": null,
"relay_switch": true,
"status": "lower_alarm",
"water_level": 0
}

with support from supplier I am almost there

@Leon3310
Copy link

I bought the same Zigbee peripheral. I'm very interested in the developments.

@syc0n
Copy link

syc0n commented Nov 15, 2024

Can you share your quirk? @brooksaw

@TheJulianJES TheJulianJES added the Tuya Request/PR regarding a Tuya device label Nov 25, 2024
@syc0n
Copy link

syc0n commented Nov 30, 2024

TUYA_TANK_LEVEL_PERCENT_DP = 22
TUYA_TANK_DEPTH_DP = 2
TUYA_TANK_STATE_DP = 1
TUYA_INSTALLATION_HEIGHT_DP = 19
TUYA_MAX_SET_DP = 7
TUYA_MIN_SET_DP = 8
TUYA_DEPTH_MAX_DP = 21

import zigpy.types as t
from zhaquirks.tuya.builder import TuyaPowerConfigurationCluster2AAA, TuyaQuirkBuilder
from zigpy.quirks.v2.homeassistant.sensor import SensorDeviceClass, SensorStateClass
from zigpy.quirks.v2.homeassistant import UnitOfLength, UnitOfTime
from zigpy.quirks.v2 import EntityPlatform, EntityType

(
    TuyaQuirkBuilder("_TZE284_kyyu8rbj", "TS0601")
    .applies_to("_TZE284_kyyu8rbj", "TS0601")
    #.tuya_temperature(dp_id=TUYA_TANK_DEPTH_DP)
    .tuya_sensor(
        dp_id=TUYA_TANK_DEPTH_DP,
        attribute_name="TUYA_TANK_DEPTH_DP",
        type=t.int16s,
        state_class=SensorStateClass.MEASUREMENT,
        device_class=SensorDeviceClass.DISTANCE,
        #unit=UnitOfLength.METERS,
        #unit=UnitOfTime.SECONDS,
        entity_type=EntityType.STANDARD,
        translation_key="TUYA_TANK_DEPTH_DP",
        fallback_name="TUYA_TANK_DEPTH_DP",
    )
    .tuya_sensor(
        dp_id=TUYA_TANK_DEPTH_DP,
        attribute_name="TUYA_TANK_DEPTH_DP",
        type=t.int16s,
        state_class=SensorStateClass.MEASUREMENT,
        device_class=SensorDeviceClass.DISTANCE,
        #unit=UnitOfLength.METERS,
        #unit=UnitOfTime.SECONDS,
        entity_type=EntityType.STANDARD,
        translation_key="TUYA_TANK_DEPTH_DP",
        fallback_name="TUYA_TANK_DEPTH_DP",
    )
    .tuya_sensor(
        dp_id=TUYA_INSTALLATION_HEIGHT_DP,
        attribute_name="TUYA_INSTALLATION_HEIGHT_DP",
        type=t.int16s,
        state_class=SensorStateClass.MEASUREMENT,
        device_class=SensorDeviceClass.DISTANCE,
        #unit=UnitOfLength.METERS,
        #unit=UnitOfTime.SECONDS,
        entity_type=EntityType.STANDARD,
        translation_key="TUYA_INSTALLATION_HEIGHT_DP",
        fallback_name="TUYA_INSTALLATION_HEIGHT_DP",
    )
    .tuya_sensor(
        dp_id=TUYA_DEPTH_MAX_DP,
        attribute_name="TUYA_DEPTH_MAX_DP",
        type=t.int16s,
        state_class=SensorStateClass.MEASUREMENT,
        device_class=SensorDeviceClass.DISTANCE,
        #unit=UnitOfLength.METERS,
        #unit=UnitOfTime.SECONDS,
        entity_type=EntityType.STANDARD,
        translation_key="TUYA_DEPTH_MAX_DP",
        fallback_name="TUYA_DEPTH_MAX_DP",
    )
    .tuya_sensor(
        dp_id=TUYA_MAX_SET_DP,
        attribute_name="TUYA_MAX_SET_DP",
        type=t.int16s,
        state_class=SensorStateClass.MEASUREMENT,
        device_class=SensorDeviceClass.DISTANCE,
        #unit=UnitOfLength.METERS,
        #unit=UnitOfTime.SECONDS,
        entity_type=EntityType.STANDARD,
        translation_key="TUYA_MAX_SET_DP",
        fallback_name="TUYA_MAX_SET_DP",
    )
    .tuya_sensor(
        dp_id=TUYA_MIN_SET_DP,
        attribute_name="TUYA_MIN_SET_DP",
        type=t.int16s,
        state_class=SensorStateClass.MEASUREMENT,
        device_class=SensorDeviceClass.DISTANCE,
        #unit=UnitOfLength.METERS,
        #unit=UnitOfTime.SECONDS,
        entity_type=EntityType.STANDARD,
        translation_key="TUYA_MIN_SET_DP",
        fallback_name="TUYA_MIN_SET_DP",
    )

    .skip_configuration()
    .add_to_registry()
)

its working, and fine for me currenlty.

I had issues with UnitOfLength, but that should be fixed

@prairiesnpr
Copy link
Collaborator

prairiesnpr commented Nov 30, 2024

Building off of @syc0n, this might expose a bit more functionality, untested, don't have the device.

from zigpy.quirks.v2 import EntityType
from zigpy.quirks.v2.homeassistant import PERCENTAGE, UnitOfLength
from zigpy.quirks.v2.homeassistant.sensor import SensorDeviceClass, SensorStateClass
import zigpy.types as t

from zhaquirks.tuya.builder import TuyaQuirkBuilder


class TuyaLiquidState(t.enum8):
    """Tuya Liquid State Enum."""

    Normal = 0x00
    Low = 0x01
    High = 0x02


(
    TuyaQuirkBuilder("_TZE284_kyyu8rbj", "TS0601")
    .tuya_enum(
        dp_id=1,
        attribute_name="liquid_state",
        enum_class=TuyaLiquidState,
        translation_key="liquid_state",
        fallback_name="Liquid state",
    )
    .tuya_sensor(
        dp_id=2,
        attribute_name="liquid_depth",
        type=t.uint16_t,
        state_class=SensorStateClass.MEASUREMENT,
        device_class=SensorDeviceClass.DISTANCE,
        unit=UnitOfLength.METERS,
        entity_type=EntityType.STANDARD,
        converter=lambda x: x / 1000,
        translation_key="liquid_depth",
        fallback_name="Liquid depth",
    )
    .tuya_sensor(
        dp_id=22,
        attribute_name="liquid_level_percent",
        type=t.uint16_t,
        state_class=SensorStateClass.MEASUREMENT,
        unit=PERCENTAGE,
        entity_type=EntityType.STANDARD,
        translation_key="liquid_level_percent",
        fallback_name="Liquid level ratio",
    )
    .tuya_number(
        dp_id=7,
        attribute_name="max_set",
        type=t.uint16_t,
        unit=PERCENTAGE,
        min_value=0,
        max_value=100,
        step=1,
        translation_key="max_set",
        fallback_name="Liquid max percentage",
    )
    .tuya_number(
        dp_id=8,
        attribute_name="mini_set",
        type=t.uint16_t,
        unit=PERCENTAGE,
        min_value=0,
        max_value=100,
        step=1,
        translation_key="mini_set",
        fallback_name="Liquid minimal percentage",
    )
    .tuya_number(
        dp_id=19,
        attribute_name="installation_height",
        type=t.uint16_t,
        device_class=SensorDeviceClass.DISTANCE,
        unit=UnitOfLength.MILLIMETERS,
        min_value=100,
        max_value=4000,
        step=1,
        translation_key="installation_height",
        fallback_name="Height from sensor to tank bottom",
    )
    .tuya_number(
        dp_id=21,
        attribute_name="liquid_depth_max",
        type=t.uint16_t,
        device_class=SensorDeviceClass.DISTANCE,
        unit=UnitOfLength.MILLIMETERS,
        min_value=100,
        max_value=2000,
        step=1,
        translation_key="liquid_depth_max",
        fallback_name="Height from sensor to liquid level",
    )
    .skip_configuration()
    .add_to_registry()
)

@gmerciel
Copy link

gmerciel commented Dec 9, 2024

Based on previous comments this is what I've got so far. Basically I moved the state to Diagnosis, removed PERCENTAGE units since values doesn't show if this is specified and corrected the distance units to match the actual values.

from zigpy.quirks.v2 import EntityType
from zigpy.quirks.v2.homeassistant import PERCENTAGE, UnitOfLength
from zigpy.quirks.v2.homeassistant.sensor import SensorDeviceClass, SensorStateClass
import zigpy.types as t

from zhaquirks.tuya.builder import TuyaQuirkBuilder


class TuyaLiquidState(t.enum8):
    """Tuya Liquid State Enum."""

    Normal = 0x00
    Low = 0x01
    High = 0x02


(
    TuyaQuirkBuilder("_TZE284_kyyu8rbj", "TS0601")
    .tuya_enum(
        dp_id=1,
        attribute_name="liquid_state",
        enum_class=TuyaLiquidState,
        entity_type=EntityType.DIAGNOSTIC,
        translation_key="liquid_state",
        fallback_name="Liquid state",
    )
    .tuya_sensor(
        dp_id=2,
        attribute_name="liquid_depth",
        type=t.uint16_t,
        state_class=SensorStateClass.MEASUREMENT,
        device_class=SensorDeviceClass.DISTANCE,
        unit=UnitOfLength.METERS,
        entity_type=EntityType.STANDARD,
        converter=lambda x: x / 100,
        translation_key="liquid_depth",
        fallback_name="Liquid depth",
    )
    .tuya_sensor(
        dp_id=22,
        attribute_name="liquid_level_percent",
        type=t.uint16_t,
        state_class=SensorStateClass.MEASUREMENT,
        entity_type=EntityType.STANDARD,
        translation_key="liquid_level_percent",
        fallback_name="Liquid level ratio",
    )
    .tuya_number(
        dp_id=7,
        attribute_name="max_set",
        type=t.uint16_t,
        min_value=0,
        max_value=100,
        step=1,
        translation_key="max_set",
        fallback_name="Liquid max percentage",
    )
    .tuya_number(
        dp_id=8,
        attribute_name="mini_set",
        type=t.uint16_t,
        min_value=0,
        max_value=100,
        step=1,
        translation_key="mini_set",
        fallback_name="Liquid minimal percentage",
    )
    .tuya_number(
        dp_id=19,
        attribute_name="installation_height",
        type=t.uint16_t,
        device_class=SensorDeviceClass.DISTANCE,
        unit=UnitOfLength.CENTIMETERS,
        min_value=10,
        max_value=400,
        step=1,
        translation_key="installation_height",
        fallback_name="Height from sensor to tank bottom",
    )
    .tuya_number(
        dp_id=21,
        attribute_name="liquid_depth_max",
        type=t.uint16_t,
        device_class=SensorDeviceClass.DISTANCE,
        unit=UnitOfLength.CENTIMETERS,
        min_value=10,
        max_value=400,
        step=1,
        translation_key="liquid_depth_max",
        fallback_name="Height from sensor to liquid level",
    )
    .skip_configuration()
    .add_to_registry()
)

@prairiesnpr
Copy link
Collaborator

Based on previous comments this is what I've got so far. Basically I moved the state to Diagnosis, removed PERCENTAGE units since values doesn't show if this is specified and corrected the distance units to match the actual values.

Is this quirk working, if so will you be submitting a PR?

@gmerciel
Copy link

It is working, altough, I'm making more tests to figure out why is the PERCENTAGE unit not working. Once I figure this out I can submit a PR.

@prairiesnpr
Copy link
Collaborator

The percent issue is in ZHA, zigpy/zha#327

@gmerciel
Copy link

gmerciel commented Dec 14, 2024

I'm having trouble with the liquid_level_percent attribute, it is constantly reporting 100.0, even when liquid_depth is showing the tank at 0.5m (out of 1.8m). Changing the value of liquid_depth_max doesn't seem to take any effect (I would assume it is used to calculate the fill percentage). It seems like this configuration doesn't exist on the sensor, if you look at the configuration options in the Tuya App, it shows only Set Max, Set Min and Installation height.

The issue with the level percent could be a problem with my sensor, so if anyone can confirm it works on their device it would be awsome.

@prairiesnpr
Copy link
Collaborator

I'm having trouble with the liquid_level_percent attribute, it is constantly reporting 100.0, even when liquid_depth is showing the tank at 0.5m (out of 1.8m).

If you could enable debug logging and grab the logs when liquid_level_percent updates, it would be interesting to see what value and type the device is actually reporting.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Tuya Request/PR regarding a Tuya device
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants