ZHA custom quirk to work with Tuya TS0601 _TZE204_dapwryy7 occupancy sensor

Hi everyone

I recently bought this occupancy sensor Tuya TS0601 _TZE204_dapwryy7 but it shows no available controls on ZHA.

I tried with some custom quirk following this guide here (it’s my first quirk integration - not sure if I’m doing it wrong):

and tried loading this custom quirk

Since my device was not listed on the python script I added this new line to the code
(“_TZE204_laokfqwu”, “TS0601”),

Deleted the existing paired device, rebooted and re-paired the device but unfortunately I still see no available controls after pairing the device to ZHA.

Anybody can point me to what I’m doing wrong?
Is there something wrong while loading thr zha quirks from configuration.yaml?

Thanks

Posting also some snapshot of my configuration.yaml (sorry the forum didn’t allow me to that in a single post)

I’m new to custom quirks but it looks like the device is not picking up the quirk

if this might help I’m running Home Assistant on Synology Nas on a dedicated VM.

You need to start by identifying the Tuya data points. Once you have those you should be able to build a quirk.

You can find them in the zigbee2mqtt source code zigbee-herdsman-converters/src/devices/tuya.ts at d999637d88e2af88e8a065fede3a0919d870ff59 · Koenkk/zigbee-herdsman-converters · GitHub, if it supported there or by using the Tuya dev console, see Find Tuya Data Points | Zigbee2MQTT. You can also get them through the diagnostics of the local Tuya integration.

Hello, here a gift (quirk v2 for this mmwave sensor) Quirk zha pour détecteur millimetrique zigbee mmwave _TZE204_dapwryy7 :wink:

2 Likes

I finally got it working with a custom quirk found here, added my own version to the code.

The presence sensor works great. Sensibility control works. However the luminance sensor does not work at all and the other available controls do not work as expected either.
Not sure why but I had to wait for a day or so before I could see all the controls unlocked.

This is great! Are the controls working with this version including luminance sensor?
I’m currently testing this and this looks awesome. It’s much better than I expected it to be!

I haven’t seen the “Luminance” changing yet. Not sure if it only changes every X amount of time.
If this does work this is going to be the PERFECT zigbee device!!

merci beaucoup

2 Likes

Thanks for your reply :slight_smile:

The luminance sensor doesn’t seem to work for me either. I’m not even sure there’s really a lux sensor in it. :confused:

When I opened it I did see a luminance sensor inside the board.
Anyway it’s no problem in my case since I’m using the current solar panel production to determine whether or not it’s dark enough to turn the lights on. It more or less does the same job of a luminance sensor.

Big thanks man! Great job!

First, thanks for the quirk! It works great, except the illuminance sensor. However, about the illuminance sensor I found the following:
If I go to “Manage Zigbee Device” and then select as cluster “HumanPresenceSensorManufCluster” and as attribute “illuminance_lux”, then via “READ ATTRIBUTE” I can get values that make sense. I tested it with a flashlight, and the value increases/decreases with increasing/decreasing brightness. Maybe it is just a mapping problem, but I have no Idea how to map it in the quirk.

Maybe someone knows how to do the mapping? Or could provide a resource that explains how to do it?

Confirmed mine working the same too.
I can see the value changing from zero up to 2500 when illuminated.

It does look like a mapping problem then

I am no quirk expert but I’ve been a coder for a while a long time ago.

I can see from the code that there is no entry for the Illuminance settings under QuirkBuilder section. There is no dedicated ‘.number’ and no ‘.binary_sensor’ field for the Illuminance.

I can instead see that defined among the dp_to_attribute section (ref ID 102) but it has no parameters defined.

OK I think I just got it working.
The Illuminance value was missing some mapping on the original quirk. I added some lines and I can now see the Illuminance sensor value moving from 0 to 2500 when illuminated. And it works pretty well too.


Give me a couple of days to twick the code a bit, test the updated quirk, and I can then share it. :wink:

1 Like

So here is the updated quirk now showing the Illuminance value and cherry on the cake the ‘hold time’ unlocked.

import logging
from typing import Final

from zigpy.quirks.v2 import QuirkBuilder, BinarySensorDeviceClass

import zigpy.types as t
from zigpy.zcl.foundation import ZCLAttributeDef
from zigpy.zcl.clusters.measurement import (
    IlluminanceMeasurement,
    OccupancySensing,
)
from zigpy.zcl.clusters.security import IasZone
from zigpy.quirks.v2.homeassistant import EntityPlatform, EntityType

from zhaquirks.tuya import (
    TuyaLocalCluster,
    TuyaPowerConfigurationCluster2AAA,
)
from zhaquirks.tuya.mcu import TuyaMCUCluster, DPToAttributeMapping

class PresenceState(t.enum8):
    """Presence State values"""
    none = 0x00
    presence = 0x01
    peaceful = 0x02
    small_movement = 0x03
    large_movement = 0x04

class TuyaOccupancySensing(OccupancySensing, TuyaLocalCluster):
    """Tuya local OccupancySensing cluster."""

class TuyaIlluminanceMeasurement(IlluminanceMeasurement, TuyaLocalCluster):
    """Tuya local IlluminanceMeasurement cluster."""

class HumanPresenceSensorManufCluster(TuyaMCUCluster):
    """Human Presence Sensor ZG-205Z (5.8GHz)"""

    class AttributeDefs(TuyaMCUCluster.AttributeDefs):
        """Tuya DataPoints attributes"""

        # Presence state
        presence_state: Final = ZCLAttributeDef(
            id=0x0001, # DP 1
            type=t.uint16_t,
            access="rp",
            is_manufacturer_specific=True,
        )
        # Target distance
        target_distance: Final = ZCLAttributeDef(
            id=0x0101, # DP 101
            type=t.uint16_t,
            access="rp",
            is_manufacturer_specific=True,
        )
        # Illuminance value
        illuminance_lux: Final = ZCLAttributeDef(
            id=0x0102, # DP 102
            type=t.uint16_t,
            access="rp",
            is_manufacturer_specific=True,
        )
        # None delay time (presence keep time)
        none_delay_time: Final = ZCLAttributeDef(
            id=0x0103, # DP 103
            type=t.uint16_t,
            is_manufacturer_specific=True,
        )
        # Indicator
        indicator: Final = ZCLAttributeDef(
            id=0x0104, # DP 104
            type=t.Bool,
            is_manufacturer_specific=True,
        )
        # Move detection max distance
        move_detection_max: Final = ZCLAttributeDef(
            id=0x0107, # DP 107
            type=t.uint16_t,
            is_manufacturer_specific=True,
        )
        # Move detection min distance
        move_detection_min: Final = ZCLAttributeDef(
            id=0x0108, # DP 108
            type=t.uint16_t,
            is_manufacturer_specific=True,
        )
        # Breath detection max distance
        breath_detection_max: Final = ZCLAttributeDef(
            id=0x0109, # DP 109
            type=t.uint16_t,
            is_manufacturer_specific=True,
        )
        # Breath detection min distance
        breath_detection_min: Final = ZCLAttributeDef(
            id=0x0110, # DP 110
            type=t.uint16_t,
            is_manufacturer_specific=True,
        )
        # Small move detection max distance
        small_move_detection_max: Final = ZCLAttributeDef(
            id=0x0114, # DP 114
            type=t.uint16_t,
            is_manufacturer_specific=True,
        )
        # Small move detection min distance
        small_move_detection_min: Final = ZCLAttributeDef(
            id=0x0115, # DP 115
            type=t.uint16_t,
            is_manufacturer_specific=True,
        )
        # Move sensitivity (1 is higher, 9 is lower)
        move_sensitivity: Final = ZCLAttributeDef(
            id=0x0116, # DP 116
            type=t.uint8_t,
            is_manufacturer_specific=True,
        )
        # Small move sensitivity
        small_move_sensitivity: Final = ZCLAttributeDef(
            id=0x0117, # DP 117
            type=t.uint8_t,
            is_manufacturer_specific=True,
        )
        # Breath sensitivity
        breath_sensitivity: Final = ZCLAttributeDef(
            id=0x0118, # DP 118
            type=t.uint8_t,
            is_manufacturer_specific=True,
        )

    dp_to_attribute: dict[int, DPToAttributeMapping] = {
        1: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "presence_state",
            converter=PresenceState
        ),
        101: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "target_distance",
            converter=lambda x: x / 100 if x is not None else 0
        ),
        102: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "illuminance_lux",
        ),
        103: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "none_delay_time",
            # Value in Tuya App is 30 after Factory reset
            #converter=lambda x: x if x is not None else 30
        ),
        104: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "indicator",
        ),
        107: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "move_detection_max",
            converter=lambda x: x / 100 if x is not None else 10
        ),
        108: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "move_detection_min",
            converter=lambda x: x / 100 if x is not None else 0
        ),
        109: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "breath_detection_max",
            converter=lambda x: x / 100 if x is not None else 6
        ),
        110: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "breath_detection_min",
            converter=lambda x: x / 100 if x is not None else 0
        ),
        114: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "small_move_detection_max",
            converter=lambda x: x / 100 if x is not None else 6
        ),
        115: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "small_move_detection_min",
            converter=lambda x: x / 100 if x is not None else 0
        ),
        116: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "move_sensitivity",
            converter=lambda x: x if x is not None else 5
        ),
        117: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "small_move_sensitivity",
            converter=lambda x: x if x is not None else 5
        ),
        118: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "breath_sensitivity",
            converter=lambda x: x if x is not None else 5
        ),
    }

    data_point_handlers = {
        1: "_dp_2_attr_update",
        101: "_dp_2_attr_update",
        102: "_dp_2_attr_update",
        103: "_dp_2_attr_update",
        104: "_dp_2_attr_update",
        107: "_dp_2_attr_update",
        108: "_dp_2_attr_update",
        109: "_dp_2_attr_update",
        110: "_dp_2_attr_update",
        114: "_dp_2_attr_update",
        115: "_dp_2_attr_update",
        116: "_dp_2_attr_update",
        117: "_dp_2_attr_update",
        118: "_dp_2_attr_update",
    }

(
    QuirkBuilder("_TZE204_dapwryy7", "TS0601")
    .skip_configuration()
    .removes(IasZone.cluster_id)
    .adds(HumanPresenceSensorManufCluster)
    #.adds(TuyaOccupancySensing)
    .replaces(TuyaPowerConfigurationCluster2AAA)
    .replaces(TuyaIlluminanceMeasurement)
    .binary_sensor(
        "presence_state",
        HumanPresenceSensorManufCluster.cluster_id,
        endpoint_id=1,
        #entity_type=EntityType.STANDARD, # very soon (zigpy channel #dev on github
        device_class=BinarySensorDeviceClass.OCCUPANCY,
        fallback_name="Presence"
    )
    .enum(
        HumanPresenceSensorManufCluster.AttributeDefs.presence_state.name,
        PresenceState,
        HumanPresenceSensorManufCluster.cluster_id,
        entity_platform=EntityPlatform.SENSOR,
        entity_type=EntityType.STANDARD,
        fallback_name="Presence State",
        translation_key="presence_state"
    )
    .number(
        HumanPresenceSensorManufCluster.AttributeDefs.illuminance_lux.name,
        HumanPresenceSensorManufCluster.cluster_id,
        step=1,
        min_value=0,
        max_value=2500,
        fallback_name="Illuminance (lx)",
        translation_key="illuminance_lux",
        #entity_type=EntityType.STANDARD # not yet :/
    )
    .number(
        HumanPresenceSensorManufCluster.AttributeDefs.target_distance.name,
        HumanPresenceSensorManufCluster.cluster_id,
        step=0.01,
        min_value=0,
        max_value=10,
        fallback_name="Target Distance (m)",
        translation_key="target_distance",
        #entity_type=EntityType.STANDARD # not yet :/
    )
    .number(
        HumanPresenceSensorManufCluster.AttributeDefs.none_delay_time.name,
        HumanPresenceSensorManufCluster.cluster_id,
        step=1,
        min_value=0,
        max_value=28800,
        #unit="s",
        fallback_name="Hold Delay Time",
        translation_key="none_delay_time"
    )
    .switch(
        HumanPresenceSensorManufCluster.AttributeDefs.indicator.name,
        HumanPresenceSensorManufCluster.cluster_id,
        fallback_name="LED Indicator",
        translation_key="indicator"
    )
    .number(
        HumanPresenceSensorManufCluster.AttributeDefs.move_detection_max.name,
        HumanPresenceSensorManufCluster.cluster_id,
        step=0.01,
        min_value=0,
        max_value=10,
        fallback_name="Move Detection Max Distance (m)",
        translation_key="move_detection_max"
    )
    .number(
        HumanPresenceSensorManufCluster.AttributeDefs.move_detection_min.name,
        HumanPresenceSensorManufCluster.cluster_id,
        step=0.01,
        min_value=0,
        max_value=10,
        fallback_name="Move Detection Min Distance (m)",
        translation_key="move_detection_min"
    )
    .number(
        HumanPresenceSensorManufCluster.AttributeDefs.small_move_detection_max.name,
        HumanPresenceSensorManufCluster.cluster_id,
        step=0.01,
        min_value=0,
        max_value=6,
        fallback_name="Small Move Detection Max Distance (m)",
        translation_key="small_move_detection_max"
    )
    .number(
        HumanPresenceSensorManufCluster.AttributeDefs.small_move_detection_min.name,
        HumanPresenceSensorManufCluster.cluster_id,
        step=0.01,
        min_value=0,
        max_value=6,
        fallback_name="Small Move Detection Min Distance (m)",
        translation_key="small_move_detection_min"
    )
    .number(
        HumanPresenceSensorManufCluster.AttributeDefs.breath_detection_max.name,
        HumanPresenceSensorManufCluster.cluster_id,
        step=0.01,
        min_value=0,
        max_value=6,
        fallback_name="Breath Detection Max Distance (m)",
        translation_key="breath_detection_max"
    )
    .number(
        HumanPresenceSensorManufCluster.AttributeDefs.breath_detection_min.name,
        HumanPresenceSensorManufCluster.cluster_id,
        step=0.01,
        min_value=0,
        max_value=6,
        fallback_name="Breath Detection Min Distance (m)",
        translation_key="breath_detection_min"
    )
    .number(
        HumanPresenceSensorManufCluster.AttributeDefs.move_sensitivity.name,
        HumanPresenceSensorManufCluster.cluster_id,
        step=1,
        min_value=0,
        max_value=10,
        fallback_name="Move Sensitivity",
        translation_key="move_sensitivity"
    )
    .number(
        HumanPresenceSensorManufCluster.AttributeDefs.small_move_sensitivity.name,
        HumanPresenceSensorManufCluster.cluster_id,
        step=1,
        min_value=0,
        max_value=10,
        fallback_name="Small Move Sensitivity",
        translation_key="small_move_sensitivity"
    )
    .number(
        HumanPresenceSensorManufCluster.AttributeDefs.breath_sensitivity.name,
        HumanPresenceSensorManufCluster.cluster_id,
        step=1,
        min_value=0,
        max_value=10,
        fallback_name="Breath Sensitivity", 
        translation_key="breath_sensitivity"
    )
    .add_to_registry()
)

Important notes:

  1. Illuminance value is set as a number by default, I couldn’t get it to work as a sensor but it might be my weak knowledge. It does work properly as a number and will change from 0 (no light) to 2500 (max detected light).
    Although you might be able to set this from the ZHA interface it won’t affect the sensor value and general behaviour. It just can’t be modified. By the way it works pretty well and can be used to run automations.
  2. Hold Time: this should be the fading time from detection to go back to sleep mode (no detection in the room). By default it’s set to 30 seconds.
    I played with this a bit and I couldn’t figure out exactly how it affects the mmwave sensor fading time…
    It looks to me that by default the ‘Occupation’ sensor (see below snapshot- bottom green box) is set to 30 seconds and this can’t go below 30 seconds but can only be higher. So the frequency update of this sendor can’t be lower than 30 seconds.
    However it seems that the ‘Presence State’ (top green box) changes when you change the ‘Hold Time’. This entry is updated real time by default and this can be used in automations too. If you are looking for real time updates target this ‘Presence State’ instead of the ‘Occupation’.
  3. Sensitivity (any value): 1 is higher, 9 is lower.
  4. Presence State values are none, large movements, peacefull (dectects presence in the room but no movements). Use the ‘none’ value if you want to trigger automations when no room occupancy detected. As said this is updated real time.

Hope that helps

Thanks a lot!

I can confirm it is working!

Did you have a chance to test the Hold Lead Time feature?
I’m just curious if you see any changes with either the Presence State or Occupation behaviour

Have exactly the same sensor, but cannot get this to detect presence. I had my own custom quirks file already with several other sensors, so merged the above into that file. It detects it via ZHA no problem, get all the configuration settings etc. The illuminance sensor reports as ‘Unknown’ however the “Illuminance (lx)” under configuration will change depending on light levels so maybe a misconfiguration in the quirk file, should be in the sensor section not the configuration?

Anyway its the Occupancy that doesn’t work for me. It never changes. Either there is something wrong with the quirk or I’m a ghost and its not picking me up. Any ideas? (have to post the file in 2 posts, as I’m exceeding the character limit to post)

import math
import logging

from typing import Dict, Optional, Tuple, Union, Final

from zigpy.profiles import zgp, zha
from zigpy.quirks import CustomDevice
import zigpy.types as t
from zigpy.zcl import foundation
from zigpy.zcl.clusters.general import (
    AnalogInput,
    AnalogOutput,
    Basic,
    GreenPowerProxy,
    Groups,
    Identify,
    Ota,
    Scenes,
    Time,
)
from zigpy.quirks.v2.homeassistant import EntityPlatform, EntityType
from zigpy.quirks.v2 import QuirkBuilder, BinarySensorDeviceClass

from zigpy.zcl.clusters.measurement import (
    IlluminanceMeasurement,
    OccupancySensing,
    RelativeHumidity,
    TemperatureMeasurement,
)
from zigpy.zcl.clusters.security import IasZone
from zigpy.zcl.foundation import ZCLAttributeDef

from zhaquirks import Bus, LocalDataCluster, MotionOnEvent
from zhaquirks.const import (
    DEVICE_TYPE,
    ENDPOINTS,
    INPUT_CLUSTERS,
    MODELS_INFO,
    MOTION_EVENT,
    OUTPUT_CLUSTERS,
    PROFILE_ID,
)
from zhaquirks.tuya import (
    NoManufacturerCluster,
    TuyaLocalCluster,
    TuyaManufCluster,
    TuyaNewManufCluster,
    TuyaPowerConfigurationCluster2AAA,
)
from zhaquirks.tuya.mcu import (
    DPToAttributeMapping,
    TuyaAttributesCluster,
    TuyaMCUCluster,
)

ZONE_TYPE = 0x0001


class PresenceState(t.enum8):
    """Presence State values"""
    none = 0x00
    presence = 0x01
    peaceful = 0x02
    small_movement = 0x03
    large_movement = 0x04

class TuyaMmwRadarSelfTest(t.enum8):
    """Mmw radar self test values."""

    TESTING = 0
    TEST_SUCCESS = 1
    TEST_FAILURE = 2
    OTHER = 3
    COMM_FAULT = 4
    RADAR_FAULT = 5


class TuyaOccupancySensing(OccupancySensing, TuyaLocalCluster):
    """Tuya local OccupancySensing cluster."""


class TuyaAnalogInput(AnalogInput, TuyaLocalCluster):
    """Tuya local AnalogInput cluster."""


class TuyaIlluminanceMeasurement(IlluminanceMeasurement, TuyaLocalCluster):
    """Tuya local IlluminanceMeasurement cluster."""


class TuyaTemperatureMeasurement(TemperatureMeasurement, TuyaLocalCluster):
    """Tuya local TemperatureMeasurement cluster."""


class TuyaRelativeHumidity(RelativeHumidity, TuyaLocalCluster):
    """Tuya local RelativeHumidity cluster."""


class TuyaMmwRadarSensitivity(TuyaAttributesCluster, AnalogOutput):
    """AnalogOutput cluster for sensitivity."""

    _CONSTANT_ATTRIBUTES = {
        AnalogOutput.AttributeDefs.description.id: "sensitivity",
        AnalogOutput.AttributeDefs.min_present_value.id: 1,
        AnalogOutput.AttributeDefs.max_present_value.id: 9,
        AnalogOutput.AttributeDefs.resolution.id: 1,
    }


class TuyaMmwRadarMinRange(TuyaAttributesCluster, AnalogOutput):
    """AnalogOutput cluster for min range."""

    _CONSTANT_ATTRIBUTES = {
        AnalogOutput.AttributeDefs.description.id: "min_range",
        AnalogOutput.AttributeDefs.min_present_value.id: 0,
        AnalogOutput.AttributeDefs.max_present_value.id: 950,
        AnalogOutput.AttributeDefs.resolution.id: 10,
        AnalogOutput.AttributeDefs.engineering_units.id: 118,  # 31: meters
    }


class TuyaMmwRadarMaxRange(TuyaAttributesCluster, AnalogOutput):
    """AnalogOutput cluster for max range."""

    _CONSTANT_ATTRIBUTES = {
        AnalogOutput.AttributeDefs.description.id: "max_range",
        AnalogOutput.AttributeDefs.min_present_value.id: 10,
        AnalogOutput.AttributeDefs.max_present_value.id: 950,
        AnalogOutput.AttributeDefs.resolution.id: 10,
        AnalogOutput.AttributeDefs.engineering_units.id: 118,  # 31: meters
    }


class TuyaMmwRadarDetectionDelay(TuyaAttributesCluster, AnalogOutput):
    """AnalogOutput cluster for detection delay."""

    _CONSTANT_ATTRIBUTES = {
        AnalogOutput.AttributeDefs.description.id: "detection_delay",
        AnalogOutput.AttributeDefs.min_present_value.id: 000,
        AnalogOutput.AttributeDefs.max_present_value.id: 20000,
        AnalogOutput.AttributeDefs.resolution.id: 100,
        AnalogOutput.AttributeDefs.engineering_units.id: 159,  # 73: seconds
    }


class TuyaMmwRadarFadingTime(TuyaAttributesCluster, AnalogOutput):
    """AnalogOutput cluster for fading time."""

    _CONSTANT_ATTRIBUTES = {
        AnalogOutput.AttributeDefs.description.id: "fading_time",
        AnalogOutput.AttributeDefs.min_present_value.id: 2000,
        AnalogOutput.AttributeDefs.max_present_value.id: 200000,
        AnalogOutput.AttributeDefs.resolution.id: 1000,
        AnalogOutput.AttributeDefs.engineering_units.id: 159,  # 73: seconds
    }


class TuyaMmwRadarTargetDistance(TuyaAttributesCluster, AnalogInput):
    """AnalogInput cluster for target distance."""

    _CONSTANT_ATTRIBUTES = {
        AnalogOutput.AttributeDefs.description.id: "target_distance",
        AnalogOutput.AttributeDefs.engineering_units.id: 118,  # 31: meters
    }


class NeoBatteryLevel(t.enum8):
    """NEO battery level enum."""

    BATTERY_FULL = 0x00
    BATTERY_HIGH = 0x01
    BATTERY_MEDIUM = 0x02
    BATTERY_LOW = 0x03
    USB_POWER = 0x04


class NeoMotionManufCluster(TuyaNewManufCluster):
    """Neo manufacturer cluster."""

    attributes = TuyaNewManufCluster.attributes.copy()
    attributes.update(
        {
            0xEF0D: ("dp_113", t.enum8, True),  # ramdom attribute ID
        }
    )

    dp_to_attribute: Dict[int, DPToAttributeMapping] = {
        101: DPToAttributeMapping(
            TuyaOccupancySensing.ep_attribute,
            "occupancy",
        ),
        104: DPToAttributeMapping(
            TuyaTemperatureMeasurement.ep_attribute,
            "measured_value",
            lambda x: x * 10,
        ),
        105: DPToAttributeMapping(
            TuyaRelativeHumidity.ep_attribute,
            "measured_value",
            lambda x: x * 100,
        ),
        113: DPToAttributeMapping(
            TuyaNewManufCluster.ep_attribute,
            "dp_113",
        ),
    }

    data_point_handlers = {
        101: "_dp_2_attr_update",
        104: "_dp_2_attr_update",
        105: "_dp_2_attr_update",
        113: "_dp_2_attr_update",
    }


class TuyaMmwRadarClusterBase(NoManufacturerCluster, TuyaMCUCluster):
    """Mmw radar cluster, base class."""

    attributes = TuyaMCUCluster.attributes.copy()
    attributes.update(
        {
            # ramdom attribute IDs
            0xEF01: ("occupancy", t.uint32_t, True),
            0xEF02: ("sensitivity", t.uint32_t, True),
            0xEF03: ("min_range", t.uint32_t, True),
            0xEF04: ("max_range", t.uint32_t, True),
            0xEF06: ("self_test", TuyaMmwRadarSelfTest, True),
            0xEF09: ("target_distance", t.uint32_t, True),
            0xEF65: ("detection_delay", t.uint32_t, True),
            0xEF66: ("fading_time", t.uint32_t, True),
            0xEF67: ("cli", t.CharacterString, True),
            0xEF68: ("illuminance", t.uint32_t, True),
        }
    )


class TuyaMmwRadarClusterV1(TuyaMmwRadarClusterBase):
    """Mmw radar cluster, variant 1."""

    dp_to_attribute: Dict[int, DPToAttributeMapping] = {
        1: DPToAttributeMapping(
            TuyaOccupancySensing.ep_attribute,
            "occupancy",
        ),
        2: DPToAttributeMapping(
            TuyaMmwRadarSensitivity.ep_attribute,
            "present_value",
        ),
        3: DPToAttributeMapping(
            TuyaMmwRadarMinRange.ep_attribute,
            "present_value",
            endpoint_id=2,
        ),
        4: DPToAttributeMapping(
            TuyaMmwRadarMaxRange.ep_attribute,
            "present_value",
            endpoint_id=3,
        ),
        6: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "self_test",
        ),
        9: DPToAttributeMapping(
            TuyaMmwRadarTargetDistance.ep_attribute,
            "present_value",
            lambda x: x / 100,
        ),
        101: DPToAttributeMapping(
            TuyaMmwRadarDetectionDelay.ep_attribute,
            "present_value",
            converter=lambda x: x * 100,
            dp_converter=lambda x: x // 100,
            endpoint_id=4,
        ),
        102: DPToAttributeMapping(
            TuyaMmwRadarFadingTime.ep_attribute,
            "present_value",
            converter=lambda x: x * 100,
            dp_converter=lambda x: x // 100,
            endpoint_id=5,
        ),
        103: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "cli",
        ),
        104: DPToAttributeMapping(
            TuyaIlluminanceMeasurement.ep_attribute,
            "measured_value",
            converter=lambda x: int(math.log10(x) * 10000 + 1) if x > 0 else int(0),
        ),
    }

    data_point_handlers = {
        1: "_dp_2_attr_update",
        2: "_dp_2_attr_update",
        3: "_dp_2_attr_update",
        4: "_dp_2_attr_update",
        6: "_dp_2_attr_update",
        9: "_dp_2_attr_update",
        101: "_dp_2_attr_update",
        102: "_dp_2_attr_update",
        103: "_dp_2_attr_update",
        104: "_dp_2_attr_update",
    }


class TuyaMmwRadarClusterV2(TuyaMmwRadarClusterBase):
    """Mmw radar cluster, variant 2."""

    dp_to_attribute: Dict[int, DPToAttributeMapping] = {
        1: DPToAttributeMapping(
            TuyaOccupancySensing.ep_attribute,
            "occupancy",
        ),
        2: DPToAttributeMapping(
            TuyaMmwRadarSensitivity.ep_attribute,
            "present_value",
        ),
        3: DPToAttributeMapping(
            TuyaMmwRadarMinRange.ep_attribute,
            "present_value",
            endpoint_id=2,
        ),
        4: DPToAttributeMapping(
            TuyaMmwRadarMaxRange.ep_attribute,
            "present_value",
            endpoint_id=3,
        ),
        9: DPToAttributeMapping(
            TuyaMmwRadarTargetDistance.ep_attribute,
            "present_value",
        ),
        101: DPToAttributeMapping(
            TuyaMmwRadarDetectionDelay.ep_attribute,
            "present_value",
            converter=lambda x: x * 100,
            dp_converter=lambda x: x // 100,
            endpoint_id=4,
        ),
        102: DPToAttributeMapping(
            TuyaMmwRadarFadingTime.ep_attribute,
            "present_value",
            converter=lambda x: x * 100,
            dp_converter=lambda x: x // 100,
            endpoint_id=5,
        ),
        104: DPToAttributeMapping(
            TuyaIlluminanceMeasurement.ep_attribute,
            "measured_value",
            converter=lambda x: int(math.log10(x) * 10000 + 1) if x > 0 else int(0),
        ),
    }

    data_point_handlers = {
        1: "_dp_2_attr_update",
        2: "_dp_2_attr_update",
        3: "_dp_2_attr_update",
        4: "_dp_2_attr_update",
        9: "_dp_2_attr_update",
        101: "_dp_2_attr_update",
        102: "_dp_2_attr_update",
        104: "_dp_2_attr_update",
    }


class TuyaMmwRadarClusterV3(TuyaMmwRadarClusterBase):
    """Tuya MMW radar cluster, variant 3."""

    dp_to_attribute: Dict[int, DPToAttributeMapping] = {
        103: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "cli",
        ),
        104: DPToAttributeMapping(
            TuyaIlluminanceMeasurement.ep_attribute,
            "measured_value",
            converter=lambda x: int(math.log10(x) * 10000 + 1) if x > 0 else int(0),
        ),
        105: DPToAttributeMapping(
            TuyaOccupancySensing.ep_attribute,
            "occupancy",
        ),
        106: DPToAttributeMapping(
            TuyaMmwRadarSensitivity.ep_attribute,
            "present_value",
        ),
        107: DPToAttributeMapping(
            TuyaMmwRadarMaxRange.ep_attribute,
            "present_value",
            endpoint_id=3,
        ),
        108: DPToAttributeMapping(
            TuyaMmwRadarMinRange.ep_attribute,
            "present_value",
            endpoint_id=2,
        ),
        109: DPToAttributeMapping(
            TuyaMmwRadarTargetDistance.ep_attribute,
            "present_value",
        ),
        110: DPToAttributeMapping(
            TuyaMmwRadarFadingTime.ep_attribute,
            "present_value",
            converter=lambda x: x * 100,
            dp_converter=lambda x: x // 100,
            endpoint_id=5,
        ),
        111: DPToAttributeMapping(
            TuyaMmwRadarDetectionDelay.ep_attribute,
            "present_value",
            converter=lambda x: x * 100,
            dp_converter=lambda x: x // 100,
            endpoint_id=4,
        ),
    }

    data_point_handlers = {
        103: "_dp_2_attr_update",
        104: "_dp_2_attr_update",
        105: "_dp_2_attr_update",
        106: "_dp_2_attr_update",
        107: "_dp_2_attr_update",
        108: "_dp_2_attr_update",
        109: "_dp_2_attr_update",
        110: "_dp_2_attr_update",
        111: "_dp_2_attr_update",
    }


class MotionCluster(LocalDataCluster, MotionOnEvent):
    """Tuya Motion Sensor."""

    _CONSTANT_ATTRIBUTES = {ZONE_TYPE: IasZone.ZoneType.Motion_Sensor}
    reset_s = 15


class TuyaManufacturerClusterMotion(TuyaManufCluster):
    """Manufacturer Specific Cluster of the Motion device."""

    def handle_cluster_request(
        self,
        hdr: foundation.ZCLHeader,
        args: Tuple[TuyaManufCluster.Command],
        *,
        dst_addressing: Optional[
            Union[t.Addressing.Group, t.Addressing.IEEE, t.Addressing.NWK]
        ] = None,
    ) -> None:
        """Handle cluster request."""
        tuya_cmd = args[0]
        self.debug("handle_cluster_request--> hdr: %s, args: %s", hdr, args)
        if hdr.command_id == 0x0001 and tuya_cmd.command_id == 1027:
            self.endpoint.device.motion_bus.listener_event(MOTION_EVENT)


class TuyaMotion(CustomDevice):
    """BW-IS3 occupancy sensor."""

    def __init__(self, *args, **kwargs):
        """Init device."""
        self.motion_bus = Bus()
        super().__init__(*args, **kwargs)

    signature = {
        #  endpoint=1 profile=260 device_type=0 device_version=0 input_clusters=[0, 3]
        #  output_clusters=[3, 25]>
        MODELS_INFO: [("_TYST11_i5j6ifxj", "5j6ifxj"), ("_TYST11_7hfcudw5", "hfcudw5")],
        ENDPOINTS: {
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.ON_OFF_SWITCH,
                INPUT_CLUSTERS: [Basic.cluster_id, Identify.cluster_id],
                OUTPUT_CLUSTERS: [Identify.cluster_id, Ota.cluster_id],
            }
        },
    }

    replacement = {
        ENDPOINTS: {
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.OCCUPANCY_SENSOR,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Identify.cluster_id,
                    MotionCluster,
                    TuyaManufacturerClusterMotion,
                ],
                OUTPUT_CLUSTERS: [Identify.cluster_id, Ota.cluster_id],
            }
        }
    }


class NeoMotion(CustomDevice):
    """NAS-PD07 occupancy sensor."""

    signature = {
        #  endpoint=1 profile=260 device_type=81 device_version=0 input_clusters=[0, 4, 5, 61184]
        #  output_clusters=[10, 25]>
        MODELS_INFO: [
            ("_TZE200_7hfcudw5", "TS0601"),
            ("_TZE200_ppuj1vem", "TS0601"),
        ],
        ENDPOINTS: {
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    NeoMotionManufCluster.cluster_id,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            }
        },
    }

    replacement = {
        ENDPOINTS: {
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.OCCUPANCY_SENSOR,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    NeoMotionManufCluster,
                    TuyaOccupancySensing,
                    TuyaTemperatureMeasurement,
                    TuyaRelativeHumidity,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            }
        }
    }


class TuyaMmwRadarOccupancyVariant1GPP(CustomDevice):
    """Millimeter wave occupancy sensor."""

    signature = {
        #  endpoint=1, profile=260, device_type=81, device_version=1,
        #  input_clusters=[4, 5, 61184, 0], output_clusters=[25, 10])
        MODELS_INFO: [
            ("_TZE200_ar0slwnd", "TS0601"),
            ("_TZE200_sfiy5tfs", "TS0601"),
            ("_TZE200_mrf6vtua", "TS0601"),
        ],
        ENDPOINTS: {
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    TuyaNewManufCluster.cluster_id,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            242: {
                # <SimpleDescriptor endpoint=242 profile=41440 device_type=97
                # input_clusters=[]
                # output_clusters=[33]
                PROFILE_ID: zgp.PROFILE_ID,
                DEVICE_TYPE: zgp.DeviceType.PROXY_BASIC,
                INPUT_CLUSTERS: [],
                OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
            },
        },
    }

    replacement = {
        ENDPOINTS: {
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.OCCUPANCY_SENSOR,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    TuyaMmwRadarClusterV1,
                    TuyaOccupancySensing,
                    TuyaAnalogInput,
                    TuyaIlluminanceMeasurement,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            242: {
                PROFILE_ID: zgp.PROFILE_ID,
                DEVICE_TYPE: zgp.DeviceType.PROXY_BASIC,
                INPUT_CLUSTERS: [],
                OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
            },
        }
    }


class TuyaMmwRadarOccupancyVariant1(CustomDevice):
    """Millimeter wave occupancy sensor."""

    signature = {
        #  endpoint=1, profile=260, device_type=81, device_version=1,
        #  input_clusters=[0, 4, 5, 61184], output_clusters=[25, 10]
        MODELS_INFO: [
            ("_TZE200_ar0slwnd", "TS0601"),
            ("_TZE200_sfiy5tfs", "TS0601"),
            ("_TZE200_mrf6vtua", "TS0601"),
            ("_TZE200_ztc6ggyl", "TS0601"),
            ("_TZE204_ztc6ggyl", "TS0601"),
            ("_TZE200_wukb7rhc", "TS0601"),
        ],
        ENDPOINTS: {
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    TuyaNewManufCluster.cluster_id,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
        },
    }

    replacement = {
        ENDPOINTS: {
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.OCCUPANCY_SENSOR,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    TuyaMmwRadarClusterV1,
                    TuyaIlluminanceMeasurement,
                    TuyaOccupancySensing,
                    TuyaMmwRadarTargetDistance,
                    TuyaMmwRadarSensitivity,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            2: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
                INPUT_CLUSTERS: [
                    TuyaMmwRadarMinRange,
                ],
                OUTPUT_CLUSTERS: [],
            },
            3: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
                INPUT_CLUSTERS: [
                    TuyaMmwRadarMaxRange,
                ],
                OUTPUT_CLUSTERS: [],
            },
            4: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
                INPUT_CLUSTERS: [
                    TuyaMmwRadarDetectionDelay,
                ],
                OUTPUT_CLUSTERS: [],
            },
            5: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
                INPUT_CLUSTERS: [
                    TuyaMmwRadarFadingTime,
                ],
                OUTPUT_CLUSTERS: [],
            },
        }
    }


class TuyaMmwRadarOccupancyVariant2(CustomDevice):
    """Mini/Ceiling Human Breathe Sensor"""

    signature = {
        #  endpoint=1, profile=260, device_type=81, device_version=1,
        #  input_clusters=[4, 5, 61184, 0], output_clusters=[25, 10]
        MODELS_INFO: [
            ("_TZE204_qasjif9e", "TS0601"),
            ("_TZE204_mtoaryre", "TS0601"),
        ],
        ENDPOINTS: {
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    TuyaNewManufCluster.cluster_id,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            242: {
                # <SimpleDescriptor endpoint=242, profile=41440, device_type=97, device_version=0, input_clusters=[], output_clusters=[33]
                # input_clusters=[]
                # output_clusters=[33]
                PROFILE_ID: zgp.PROFILE_ID,
                DEVICE_TYPE: zgp.DeviceType.PROXY_BASIC,
                INPUT_CLUSTERS: [],
                OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
            },
        },
    }

    replacement = {
        ENDPOINTS: {
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.OCCUPANCY_SENSOR,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    TuyaMmwRadarClusterV2,
                    TuyaIlluminanceMeasurement,
                    TuyaOccupancySensing,
                    TuyaMmwRadarTargetDistance,
                    TuyaMmwRadarSensitivity,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            2: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
                INPUT_CLUSTERS: [
                    TuyaMmwRadarMinRange,
                ],
                OUTPUT_CLUSTERS: [],
            },
            3: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
                INPUT_CLUSTERS: [
                    TuyaMmwRadarMaxRange,
                ],
                OUTPUT_CLUSTERS: [],
            },
            4: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
                INPUT_CLUSTERS: [
                    TuyaMmwRadarDetectionDelay,
                ],
                OUTPUT_CLUSTERS: [],
            },
            5: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
                INPUT_CLUSTERS: [
                    TuyaMmwRadarFadingTime,
                ],
                OUTPUT_CLUSTERS: [],
            },
            242: {
                PROFILE_ID: zgp.PROFILE_ID,
                DEVICE_TYPE: zgp.DeviceType.PROXY_BASIC,
                INPUT_CLUSTERS: [],
                OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
            },
        }
    }


class TuyaMmwRadarOccupancyVariant3(CustomDevice):
    """Mini/Ceiling Human Breathe Sensor"""

    signature = {
        #  endpoint=1, profile=260, device_type=81, device_version=1,
        #  input_clusters=[0, 4, 5, 61184], output_clusters=[25, 10])
        MODELS_INFO: [
            ("_TZE204_sxm7l9xa", "TS0601"),
            ("_TZE204_e5m9c5hl", "TS0601"),
        ],
        ENDPOINTS: {
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    TuyaNewManufCluster.cluster_id,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            242: {
                # <SimpleDescriptor endpoint=242 profile=41440 device_type=97
                # input_clusters=[]
                # output_clusters=[33]
                PROFILE_ID: zgp.PROFILE_ID,
                DEVICE_TYPE: zgp.DeviceType.PROXY_BASIC,
                INPUT_CLUSTERS: [],
                OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
            },
        },
    }

    replacement = {
        ENDPOINTS: {
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.OCCUPANCY_SENSOR,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    TuyaMmwRadarClusterV3,
                    TuyaIlluminanceMeasurement,
                    TuyaOccupancySensing,
                    TuyaMmwRadarTargetDistance,
                    TuyaMmwRadarSensitivity,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            2: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
                INPUT_CLUSTERS: [
                    TuyaMmwRadarMinRange,
                ],
                OUTPUT_CLUSTERS: [],
            },
            3: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
                INPUT_CLUSTERS: [
                    TuyaMmwRadarMaxRange,
                ],
                OUTPUT_CLUSTERS: [],
            },
            4: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
                INPUT_CLUSTERS: [
                    TuyaMmwRadarDetectionDelay,
                ],
                OUTPUT_CLUSTERS: [],
            },
            5: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
                INPUT_CLUSTERS: [
                    TuyaMmwRadarFadingTime,
                ],
                OUTPUT_CLUSTERS: [],
            },
            242: {
                PROFILE_ID: zgp.PROFILE_ID,
                DEVICE_TYPE: zgp.DeviceType.PROXY_BASIC,
                INPUT_CLUSTERS: [],
                OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
            },
        }
    }
    

Rest of file…

class HumanPresenceSensorManufCluster(TuyaMCUCluster):
    """Human Presence Sensor ZG-205Z (5.8GHz)"""

    class AttributeDefs(TuyaMCUCluster.AttributeDefs):
        """Tuya DataPoints attributes"""

        # Presence state
        presence_state: Final = ZCLAttributeDef(
            id=0x0001, # DP 1
            type=t.uint16_t,
            access="rp",
            is_manufacturer_specific=True,
        )
        # Target distance
        target_distance: Final = ZCLAttributeDef(
            id=0x0101, # DP 101
            type=t.uint16_t,
            access="rp",
            is_manufacturer_specific=True,
        )
        # Illuminance value
        illuminance_lux: Final = ZCLAttributeDef(
            id=0x0102, # DP 102
            type=t.uint16_t,
            access="rp",
            is_manufacturer_specific=True,
        )
        # None delay time (presence keep time)
        none_delay_time: Final = ZCLAttributeDef(
            id=0x0103, # DP 103
            type=t.uint16_t,
            is_manufacturer_specific=True,
        )
        # Indicator
        indicator: Final = ZCLAttributeDef(
            id=0x0104, # DP 104
            type=t.Bool,
            is_manufacturer_specific=True,
        )
        # Move detection max distance
        move_detection_max: Final = ZCLAttributeDef(
            id=0x0107, # DP 107
            type=t.uint16_t,
            is_manufacturer_specific=True,
        )
        # Move detection min distance
        move_detection_min: Final = ZCLAttributeDef(
            id=0x0108, # DP 108
            type=t.uint16_t,
            is_manufacturer_specific=True,
        )
        # Breath detection max distance
        breath_detection_max: Final = ZCLAttributeDef(
            id=0x0109, # DP 109
            type=t.uint16_t,
            is_manufacturer_specific=True,
        )
        # Breath detection min distance
        breath_detection_min: Final = ZCLAttributeDef(
            id=0x0110, # DP 110
            type=t.uint16_t,
            is_manufacturer_specific=True,
        )
        # Small move detection max distance
        small_move_detection_max: Final = ZCLAttributeDef(
            id=0x0114, # DP 114
            type=t.uint16_t,
            is_manufacturer_specific=True,
        )
        # Small move detection min distance
        small_move_detection_min: Final = ZCLAttributeDef(
            id=0x0115, # DP 115
            type=t.uint16_t,
            is_manufacturer_specific=True,
        )
        # Move sensitivity (1 is higher, 9 is lower)
        move_sensitivity: Final = ZCLAttributeDef(
            id=0x0116, # DP 116
            type=t.uint8_t,
            is_manufacturer_specific=True,
        )
        # Small move sensitivity
        small_move_sensitivity: Final = ZCLAttributeDef(
            id=0x0117, # DP 117
            type=t.uint8_t,
            is_manufacturer_specific=True,
        )
        # Breath sensitivity
        breath_sensitivity: Final = ZCLAttributeDef(
            id=0x0118, # DP 118
            type=t.uint8_t,
            is_manufacturer_specific=True,
        )

    dp_to_attribute: dict[int, DPToAttributeMapping] = {
        1: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "presence_state",
            converter=PresenceState
        ),
        101: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "target_distance",
            converter=lambda x: x / 100 if x is not None else 0
        ),
        102: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "illuminance_lux",
        ),
        103: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "none_delay_time",
            # Value in Tuya App is 30 after Factory reset
            #converter=lambda x: x if x is not None else 30
        ),
        104: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "indicator",
        ),
        107: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "move_detection_max",
            converter=lambda x: x / 100 if x is not None else 10
        ),
        108: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "move_detection_min",
            converter=lambda x: x / 100 if x is not None else 0
        ),
        109: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "breath_detection_max",
            converter=lambda x: x / 100 if x is not None else 6
        ),
        110: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "breath_detection_min",
            converter=lambda x: x / 100 if x is not None else 0
        ),
        114: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "small_move_detection_max",
            converter=lambda x: x / 100 if x is not None else 6
        ),
        115: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "small_move_detection_min",
            converter=lambda x: x / 100 if x is not None else 0
        ),
        116: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "move_sensitivity",
            converter=lambda x: x if x is not None else 5
        ),
        117: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "small_move_sensitivity",
            converter=lambda x: x if x is not None else 5
        ),
        118: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "breath_sensitivity",
            converter=lambda x: x if x is not None else 5
        ),
    }

    data_point_handlers = {
        1: "_dp_2_attr_update",
        101: "_dp_2_attr_update",
        102: "_dp_2_attr_update",
        103: "_dp_2_attr_update",
        104: "_dp_2_attr_update",
        107: "_dp_2_attr_update",
        108: "_dp_2_attr_update",
        109: "_dp_2_attr_update",
        110: "_dp_2_attr_update",
        114: "_dp_2_attr_update",
        115: "_dp_2_attr_update",
        116: "_dp_2_attr_update",
        117: "_dp_2_attr_update",
        118: "_dp_2_attr_update",
    }

(
    QuirkBuilder("_TZE204_dapwryy7", "TS0601")
    .skip_configuration()
    .removes(IasZone.cluster_id)
    .adds(HumanPresenceSensorManufCluster)
    #.adds(TuyaOccupancySensing)
    .replaces(TuyaPowerConfigurationCluster2AAA)
    .replaces(TuyaIlluminanceMeasurement)
    .binary_sensor(
        "presence_state",
        HumanPresenceSensorManufCluster.cluster_id,
        endpoint_id=1,
        #entity_type=EntityType.STANDARD, # very soon (zigpy channel #dev on github
        device_class=BinarySensorDeviceClass.OCCUPANCY,
        fallback_name="Presence"
    )
    .enum(
        HumanPresenceSensorManufCluster.AttributeDefs.presence_state.name,
        PresenceState,
        HumanPresenceSensorManufCluster.cluster_id,
        entity_platform=EntityPlatform.SENSOR,
        entity_type=EntityType.STANDARD,
        fallback_name="Presence State",
        translation_key="presence_state"
    )
    .number(
        HumanPresenceSensorManufCluster.AttributeDefs.illuminance_lux.name,
        HumanPresenceSensorManufCluster.cluster_id,
        step=1,
        min_value=0,
        max_value=2500,
        fallback_name="Illuminance (lx)",
        translation_key="illuminance_lux",
        #entity_type=EntityType.STANDARD # not yet :/
    )
    .number(
        HumanPresenceSensorManufCluster.AttributeDefs.target_distance.name,
        HumanPresenceSensorManufCluster.cluster_id,
        step=0.01,
        min_value=0,
        max_value=10,
        fallback_name="Target Distance (m)",
        translation_key="target_distance",
        #entity_type=EntityType.STANDARD # not yet :/
    )
    .number(
        HumanPresenceSensorManufCluster.AttributeDefs.none_delay_time.name,
        HumanPresenceSensorManufCluster.cluster_id,
        step=1,
        min_value=0,
        max_value=28800,
        #unit="s",
        fallback_name="Hold Delay Time",
        translation_key="none_delay_time"
    )
    .switch(
        HumanPresenceSensorManufCluster.AttributeDefs.indicator.name,
        HumanPresenceSensorManufCluster.cluster_id,
        fallback_name="LED Indicator",
        translation_key="indicator"
    )
    .number(
        HumanPresenceSensorManufCluster.AttributeDefs.move_detection_max.name,
        HumanPresenceSensorManufCluster.cluster_id,
        step=0.01,
        min_value=0,
        max_value=10,
        fallback_name="Move Detection Max Distance (m)",
        translation_key="move_detection_max"
    )
    .number(
        HumanPresenceSensorManufCluster.AttributeDefs.move_detection_min.name,
        HumanPresenceSensorManufCluster.cluster_id,
        step=0.01,
        min_value=0,
        max_value=10,
        fallback_name="Move Detection Min Distance (m)",
        translation_key="move_detection_min"
    )
    .number(
        HumanPresenceSensorManufCluster.AttributeDefs.small_move_detection_max.name,
        HumanPresenceSensorManufCluster.cluster_id,
        step=0.01,
        min_value=0,
        max_value=6,
        fallback_name="Small Move Detection Max Distance (m)",
        translation_key="small_move_detection_max"
    )
    .number(
        HumanPresenceSensorManufCluster.AttributeDefs.small_move_detection_min.name,
        HumanPresenceSensorManufCluster.cluster_id,
        step=0.01,
        min_value=0,
        max_value=6,
        fallback_name="Small Move Detection Min Distance (m)",
        translation_key="small_move_detection_min"
    )
    .number(
        HumanPresenceSensorManufCluster.AttributeDefs.breath_detection_max.name,
        HumanPresenceSensorManufCluster.cluster_id,
        step=0.01,
        min_value=0,
        max_value=6,
        fallback_name="Breath Detection Max Distance (m)",
        translation_key="breath_detection_max"
    )
    .number(
        HumanPresenceSensorManufCluster.AttributeDefs.breath_detection_min.name,
        HumanPresenceSensorManufCluster.cluster_id,
        step=0.01,
        min_value=0,
        max_value=6,
        fallback_name="Breath Detection Min Distance (m)",
        translation_key="breath_detection_min"
    )
    .number(
        HumanPresenceSensorManufCluster.AttributeDefs.move_sensitivity.name,
        HumanPresenceSensorManufCluster.cluster_id,
        step=1,
        min_value=0,
        max_value=10,
        fallback_name="Move Sensitivity",
        translation_key="move_sensitivity"
    )
    .number(
        HumanPresenceSensorManufCluster.AttributeDefs.small_move_sensitivity.name,
        HumanPresenceSensorManufCluster.cluster_id,
        step=1,
        min_value=0,
        max_value=10,
        fallback_name="Small Move Sensitivity",
        translation_key="small_move_sensitivity"
    )
    .number(
        HumanPresenceSensorManufCluster.AttributeDefs.breath_sensitivity.name,
        HumanPresenceSensorManufCluster.cluster_id,
        step=1,
        min_value=0,
        max_value=10,
        fallback_name="Breath Sensitivity", 
        translation_key="breath_sensitivity"
    )
    .add_to_registry()
)

How are you identifying your Tuya data points? They move around based on the device, I’d start there.

The Illuminance value is supposed to show up in the configs section only. I couldn’t get it to work among the sensors section and I didn’t have enough time to deep dive on it.
But it works and can be used for any automation via the entity value.

For the rest your issue could be anywhere with Tuya data points. Can’t you load this quirk as a separate file?
It’s working for 2 of us with the exact same hardware so it should just work for you too.