TZE200_lvkk0hdg ZHA

I have purchased several ultrasonic sensors TZE200_lvkk0hdg
Those are absolutely great sensors and very useful in household and garden, to be used both for water storage tanks, drainage tanks, swimming pool levels etc…

Unfortunatelly I have managed to pair them just with TUYA app on my phone, byt even local tuya cant see levels in HA.
I have tried to make a custom quirk for it but it doesnt work:

"""Custom quirk for EPTTECH TLC2206-ZB Tank Level Sensor."""

from zigpy.profiles import zha
from zigpy.quirks import CustomDevice
from zigpy.zcl.clusters.general import Basic, Groups, Scenes, Ota, Time
from zigpy.zcl.clusters.measurement import FlowMeasurement
from zhaquirks.const import (
    DEVICE_TYPE,
    ENDPOINTS,
    INPUT_CLUSTERS,
    MODELS_INFO,
    OUTPUT_CLUSTERS,
    PROFILE_ID,
)
from zhaquirks.tuya import TuyaLocalCluster
from zhaquirks.tuya.mcu import DPToAttributeMapping, TuyaMCUCluster
import zigpy.types as t

# Define the data points (DPs) for the sensor
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

class LiquidLevelStatus(t.enum8):
    """Enum for liquid level status."""
    NORMAL = 0x00
    LOW = 0x01
    HIGH = 0x02

class TuyaTankLevelMeasurement(TuyaLocalCluster):
    """Custom cluster for tank level measurement."""

    cluster_id = FlowMeasurement.cluster_id
    name = "Tank Level Measurement"
    ep_attribute = "liquid_level_measurement"

    attributes = {
        0x0000: ("measured_value", t.uint16_t, True),
        0x0001: ("min_measured_value", t.uint16_t, True),
        0x0002: ("max_measured_value", t.uint16_t, True),
        0xFF00: ("liquid_level_percent", t.uint8_t, True),
        0xFF01: ("liquid_depth", t.uint16_t, True),  # Assuming depth is in mm
        0xFF02: ("liquid_state", LiquidLevelStatus, True),
        0xFF03: ("installation_height", t.uint16_t, True),  # mm
        0xFF04: ("max_set", t.uint8_t, True),  # Percentage
        0xFF05: ("mini_set", t.uint8_t, True),  # Percentage
        0xFF06: ("liquid_depth_max", t.uint16_t, True),  # mm
    }

class TuyaTankLevelCluster(TuyaMCUCluster):
    """Custom cluster for handling EPTTECH TLC2206-ZB tank level sensor data points."""

    dp_to_attribute: dict[int, DPToAttributeMapping] = {
        TUYA_TANK_LEVEL_PERCENT_DP: DPToAttributeMapping(
            TuyaTankLevelMeasurement.ep_attribute,
            "liquid_level_percent",
        ),
        TUYA_TANK_DEPTH_DP: DPToAttributeMapping(
            TuyaTankLevelMeasurement.ep_attribute,
            "liquid_depth",
            converter=lambda x: x / 100,  # Convert mm to meters
        ),
        TUYA_TANK_STATE_DP: DPToAttributeMapping(
            TuyaTankLevelMeasurement.ep_attribute,
            "liquid_state",
            converter=lambda x: LiquidLevelStatus(x),
        ),
        TUYA_INSTALLATION_HEIGHT_DP: DPToAttributeMapping(
            TuyaTankLevelMeasurement.ep_attribute,
            "installation_height",
        ),
        TUYA_MAX_SET_DP: DPToAttributeMapping(
            TuyaTankLevelMeasurement.ep_attribute,
            "max_set",
        ),
        TUYA_MIN_SET_DP: DPToAttributeMapping(
            TuyaTankLevelMeasurement.ep_attribute,
            "mini_set",
        ),
        TUYA_DEPTH_MAX_DP: DPToAttributeMapping(
            TuyaTankLevelMeasurement.ep_attribute,
            "liquid_depth_max",
        ),
    }

    data_point_handlers = {
        TUYA_TANK_LEVEL_PERCENT_DP: "_dp_2_attr_update",
        TUYA_TANK_DEPTH_DP: "_dp_2_attr_update",
        TUYA_TANK_STATE_DP: "_dp_2_attr_update",
        TUYA_INSTALLATION_HEIGHT_DP: "_dp_2_attr_update",
        TUYA_MAX_SET_DP: "_dp_2_attr_update",
        TUYA_MIN_SET_DP: "_dp_2_attr_update",
        TUYA_DEPTH_MAX_DP: "_dp_2_attr_update",
    }

class EPTTECH_TLC2206ZB(CustomDevice):
    """Custom quirk for EPTTECH TLC2206-ZB Tank Level Sensor."""

    signature = {
        MODELS_INFO: [("TS0601", "_TZE200_lvkk0hdg")],
        ENDPOINTS: {
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.IAS_ZONE,  # Device type commonly used for sensors
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    TuyaTankLevelCluster.cluster_id,  # Custom cluster for tank level
                ],
                OUTPUT_CLUSTERS: [
                    Ota.cluster_id,
                    Time.cluster_id,
                ],
            },
        },
    }

    replacement = {
        ENDPOINTS: {
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.IAS_ZONE,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    TuyaTankLevelCluster,  # Custom cluster for tank level
                ],
                OUTPUT_CLUSTERS: [
                    Ota.cluster_id,
                    Time.cluster_id,
                ],
            },
        },
    }

Could ZHA development team add support for the device like mqtt2zigbee did?

1 Like

Have you managed to get it working via ZHA ?

yes, custom quirk:


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 LiquidLevelStatus(t.enum8):
    """Enum for liquid level status."""
    NORMAL = 0x00
    LOW = 0x01
    HIGH = 0x02

(
    TuyaQuirkBuilder("_TZE200_lvkk0hdg", "TS0601")
    .tuya_enum(
        dp_id=1,
        attribute_name="liquid_state",
        enum_class=LiquidLevelStatus,
        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.CENTIMETERS,
        entity_type=EntityType.STANDARD,
        translation_key="liquid_depth",
        fallback_name="Liquid depth",
    )
    .tuya_sensor(
        dp_id=22,
        attribute_name="liquid_level_percent",
        type=t.uint8_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.uint8_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.uint8_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.MILLIMETERS,
        min_value=10,
        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=10,
        max_value=4000,
        step=1,
        translation_key="liquid_depth_max",
        fallback_name="Height from sensor to liquid level",
    )
    .skip_configuration()
    .add_to_registry()
)

Thanks,
I also suggested some improvements in here
[BUG] Not working in HA - ZHA support for TZE200_lvkk0hdg · Issue #4022 · zigpy/zha-device-handlers · GitHub.

  1. Use the same syntax as the official Tuya.
    a. Installation Height
    b. Max liquid depth
    in the same order and not the other way around so that words are not chopped.
    In Tuya it makes sense to display decimal as its reported at cm resolution 1.88m, however, the quirk is already in cm so 88.0 cm is better displayed as 88 cm. I had to create a new template for rounding then for my cards. Same goes for percentage, its never reported in decimal: