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] TZE204_ex3rcdha presence and lux sensor #3309

Open
agf102 opened this issue Aug 22, 2024 · 21 comments · May be fixed by #3887
Open

[Device Support Request] TZE204_ex3rcdha presence and lux sensor #3309

agf102 opened this issue Aug 22, 2024 · 21 comments · May be fixed by #3887
Labels
device support request This requests support for a new device Tuya Request/PR regarding a Tuya device

Comments

@agf102
Copy link

agf102 commented Aug 22, 2024

Problem description

I've just bought a couple of these from Aliexpress (https://www.aliexpress.com/item/1005007335302133.html) and have been playing around to see if they work in HA / ZHA. I copied a quirks file I found on the HA forum and added the model references in.

I can now see controls for detection delay; fading time; max / min range; sensitivity and can see sensors for illuminance (shows unknown), occupancy (shows clear) and firmware (shows up to date)

Solution description

Correctly operating illuminance and occupancy sensors

Screenshots/Video

Screenshots/Video

[Paste/upload your media here]

Device signature

Device signature

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
{
"node_descriptor": {
"logical_type": 1,
"complex_descriptor_available": 0,
"user_descriptor_available": 0,
"reserved": 0,
"aps_flags": 0,
"frequency_band": 8,
"mac_capability_flags": 142,
"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": "0x0107",
"input_clusters": [
"0x0000",
"0x0004",
"0x0005",
"0x000c",
"0x000d",
"0x0400",
"0x0406",
"0xef00"
],
"output_clusters": [
"0x000a",
"0x0019"
]
},
"2": {
"profile_id": "0x0104",
"device_type": "0x0007",
"input_clusters": [
"0x000d"
],
"output_clusters": []
},
"3": {
"profile_id": "0x0104",
"device_type": "0x0007",
"input_clusters": [
"0x000d"
],
"output_clusters": []
},
"4": {
"profile_id": "0x0104",
"device_type": "0x0007",
"input_clusters": [
"0x000d"
],
"output_clusters": []
},
"5": {
"profile_id": "0x0104",
"device_type": "0x0007",
"input_clusters": [
"0x000d"
],
"output_clusters": []
},
"242": {
"profile_id": "0xa1e0",
"device_type": "0x0061",
"input_clusters": [],
"output_clusters": [
"0x0021"
]
}
},
"manufacturer": "_TZE204_ex3rcdha",
"model": "TS0601",
"class": "ts0601_radar.TuyaMmwRadarOccupancyVariant1GPP"
}

Diagnostic information

Diagnostic information

{
"home_assistant": {
"installation_type": "Home Assistant OS",
"version": "2024.8.2",
"dev": false,
"hassio": true,
"virtualenv": false,
"python_version": "3.12.4",
"docker": true,
"arch": "x86_64",
"timezone": "Europe/London",
"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": {
"truenas": {
"documentation": "https://github.com/tomaae/homeassistant-truenas",
"version": "0.0.0",
"requirements": []
},
"spook_inverse": {
"documentation": "https://spook.boo",
"version": "3.1.0",
"requirements": []
},
"frigate": {
"documentation": "https://github.com/blakeblackshear/frigate",
"version": "5.3.0",
"requirements": [
"pytz"
]
},
"hypervolt_charger": {
"documentation": "https://github.com/gndean/home-assistant-hypervolt-charger",
"version": "2.3.2",
"requirements": [
"websockets==11.0.3",
"aiofiles==24.1.0"
]
},
"octopus_energy": {
"documentation": "https://bottlecapdave.github.io/HomeAssistant-OctopusEnergy",
"version": "12.2.0",
"requirements": []
},
"spook": {
"documentation": "https://spook.boo",
"version": "3.1.0",
"requirements": []
},
"mass": {
"documentation": "https://music-assistant.io",
"version": "2024.8.0",
"requirements": [
"music-assistant==2.1.3"
]
},
"scrypted": {
"documentation": "https://www.home-assistant.io/integrations/scrypted",
"version": "0.0.9",
"requirements": []
},
"foxess": {
"documentation": "https://github.com/macxq/foxess-ha",
"version": "v0.4",
"requirements": [
"random_user_agent"
]
},
"uptime_kuma": {
"documentation": "https://github.com/meichthys/uptime_kuma/blob/main/README.md",
"version": "2.1.0",
"requirements": [
"pyuptimekuma-hass"
]
},
"powercalc": {
"documentation": "https://docs.powercalc.nl",
"version": "v1.14.2",
"requirements": [
"numpy>=1.21.1"
]
},
"flightradar24": {
"documentation": "https://github.com/AlexandrErohin/home-assistant-flightradar24",
"version": "1.15.0",
"requirements": [
"FlightRadarAPI==1.3.25",
"pycountry==23.12.11"
]
},
"hacs": {
"documentation": "https://hacs.xyz/docs/configuration/start",
"version": "2.0.0",
"requirements": [
"aiogithubapi>=22.10.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.31"
],
"usb": [
{
"vid": "10C4",
"pid": "EA60",
"description": "2652",
"known_devices": [
"slae.sh cc2652rb stick"
]
},
{
"vid": "10C4",
"pid": "EA60",
"description": "slzb-07",
"known_devices": [
"smlight slzb-07"
]
},
{
"vid": "1A86",
"pid": "55D4",
"description": "sonoffplus*",
"known_devices": [
"sonoff zigbee dongle plus v2"
]
},
{
"vid": "10C4",
"pid": "EA60",
"description": "sonoffplus*",
"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": 0.00018496799999923041
},
"1939130f5ea2867d4ad08cc2b59a92ce": {
"wait_import_platforms": -0.07864109599999836,
"config_entry_setup": 20.42540425099999
}
},
"data": {
"ieee": "REDACTED",
"nwk": 9290,
"manufacturer": "_TZE204_ex3rcdha",
"model": "TS0601",
"name": "_TZE204_ex3rcdha TS0601",
"quirk_applied": true,
"quirk_class": "ts0601_radar.TuyaMmwRadarOccupancyVariant1GPP",
"quirk_id": null,
"manufacturer_code": 4417,
"power_source": "Mains",
"lqi": 36,
"rssi": null,
"last_seen": "2024-08-22T10:21:22",
"available": true,
"device_type": "Router",
"signature": {
"node_descriptor": {
"logical_type": 1,
"complex_descriptor_available": 0,
"user_descriptor_available": 0,
"reserved": 0,
"aps_flags": 0,
"frequency_band": 8,
"mac_capability_flags": 142,
"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": "0x0107",
"input_clusters": [
"0x0000",
"0x0004",
"0x0005",
"0x000c",
"0x000d",
"0x0400",
"0x0406",
"0xef00"
],
"output_clusters": [
"0x000a",
"0x0019"
]
},
"2": {
"profile_id": "0x0104",
"device_type": "0x0007",
"input_clusters": [
"0x000d"
],
"output_clusters": []
},
"3": {
"profile_id": "0x0104",
"device_type": "0x0007",
"input_clusters": [
"0x000d"
],
"output_clusters": []
},
"4": {
"profile_id": "0x0104",
"device_type": "0x0007",
"input_clusters": [
"0x000d"
],
"output_clusters": []
},
"5": {
"profile_id": "0x0104",
"device_type": "0x0007",
"input_clusters": [
"0x000d"
],
"output_clusters": []
},
"242": {
"profile_id": "0xa1e0",
"device_type": "0x0061",
"input_clusters": [],
"output_clusters": [
"0x0021"
]
}
},
"manufacturer": "_TZE204_ex3rcdha",
"model": "TS0601"
},
"active_coordinator": false,
"entities": [
{
"entity_id": "binary_sensor.zigbee_presence_2_small_occupancy",
"name": "_TZE204_ex3rcdha TS0601"
},
{
"entity_id": "number.zigbee_presence_2_small_number_sensitivity",
"name": "_TZE204_ex3rcdha TS0601"
},
{
"entity_id": "number.zigbee_presence_2_small_number_min_range",
"name": "_TZE204_ex3rcdha TS0601"
},
{
"entity_id": "number.zigbee_presence_2_small_number_max_range",
"name": "_TZE204_ex3rcdha TS0601"
},
{
"entity_id": "number.zigbee_presence_2_small_number_detection_delay",
"name": "_TZE204_ex3rcdha TS0601"
},
{
"entity_id": "number.zigbee_presence_2_small_number_fading_time",
"name": "_TZE204_ex3rcdha TS0601"
},
{
"entity_id": "sensor.zigbee_presence_2_small_illuminance",
"name": "_TZE204_ex3rcdha TS0601"
},
{
"entity_id": "update.zigbee_presence_2_small_firmware",
"name": "_TZE204_ex3rcdha TS0601"
}
],
"neighbors": [
{
"device_type": "Router",
"rx_on_when_idle": "On",
"relationship": "Parent",
"extended_pan_id": "REDACTED",
"ieee": "REDACTED",
"nwk": "0x1B1F",
"permit_joining": "Unknown",
"depth": "2",
"lqi": "151"
},
{
"device_type": "Router",
"rx_on_when_idle": "On",
"relationship": "Sibling",
"extended_pan_id": "REDACTED",
"ieee": "REDACTED",
"nwk": "0x771B",
"permit_joining": "Unknown",
"depth": "0",
"lqi": "15"
},
{
"device_type": "Router",
"rx_on_when_idle": "On",
"relationship": "Sibling",
"extended_pan_id": "REDACTED",
"ieee": "REDACTED",
"nwk": "0x7A23",
"permit_joining": "Unknown",
"depth": "0",
"lqi": "51"
},
{
"device_type": "Router",
"rx_on_when_idle": "On",
"relationship": "Sibling",
"extended_pan_id": "REDACTED",
"ieee": "REDACTED",
"nwk": "0x4D60",
"permit_joining": "Unknown",
"depth": "0",
"lqi": "69"
},
{
"device_type": "Router",
"rx_on_when_idle": "On",
"relationship": "Sibling",
"extended_pan_id": "REDACTED",
"ieee": "REDACTED",
"nwk": "0x1EE3",
"permit_joining": "Unknown",
"depth": "0",
"lqi": "15"
},
{
"device_type": "Coordinator",
"rx_on_when_idle": "On",
"relationship": "Sibling",
"extended_pan_id": "REDACTED",
"ieee": "REDACTED",
"nwk": "0x0000",
"permit_joining": "Unknown",
"depth": "0",
"lqi": "36"
},
{
"device_type": "Router",
"rx_on_when_idle": "On",
"relationship": "Sibling",
"extended_pan_id": "REDACTED",
"ieee": "REDACTED",
"nwk": "0x242D",
"permit_joining": "Unknown",
"depth": "0",
"lqi": "30"
},
{
"device_type": "Router",
"rx_on_when_idle": "On",
"relationship": "Sibling",
"extended_pan_id": "REDACTED",
"ieee": "REDACTED",
"nwk": "0x46E5",
"permit_joining": "Unknown",
"depth": "0",
"lqi": "0"
}
],
"routes": [],
"endpoint_names": [
{
"name": "OCCUPANCY_SENSOR"
},
{
"name": "COMBINED_INTERFACE"
},
{
"name": "COMBINED_INTERFACE"
},
{
"name": "COMBINED_INTERFACE"
},
{
"name": "COMBINED_INTERFACE"
},
{
"name": "PROXY_BASIC"
}
],
"user_given_name": "ZigBee Presence 2 small",
"device_reg_id": "6a3b86ff04cd4ccc64ae5027c75088d6",
"area_id": null,
"cluster_details": {
"1": {
"device_type": {
"name": "OCCUPANCY_SENSOR",
"id": 263
},
"profile_id": 260,
"in_clusters": {
"0x0000": {
"endpoint_attribute": "basic",
"attributes": {
"0x0001": {
"attribute_name": "app_version",
"value": 74
},
"0x0004": {
"attribute_name": "manufacturer",
"value": "_TZE204_ex3rcdha"
},
"0x0005": {
"attribute_name": "model",
"value": "TS0601"
}
},
"unsupported_attributes": {}
},
"0x0004": {
"endpoint_attribute": "groups",
"attributes": {},
"unsupported_attributes": {}
},
"0x0005": {
"endpoint_attribute": "scenes",
"attributes": {},
"unsupported_attributes": {}
},
"0xef00": {
"endpoint_attribute": "tuya_manufacturer",
"attributes": {
"0xef00": {
"attribute_name": "mcu_version",
"value": "1.0.2"
}
},
"unsupported_attributes": {}
},
"0x0400": {
"endpoint_attribute": "illuminance",
"attributes": {},
"unsupported_attributes": {
"0x0000": {
"attribute_name": "measured_value"
}
}
},
"0x0406": {
"endpoint_attribute": "occupancy",
"attributes": {},
"unsupported_attributes": {
"0x0000": {
"attribute_name": "occupancy"
}
}
},
"0x000c": {
"endpoint_attribute": "analog_input",
"attributes": {
"0x001c": {
"attribute_name": "description",
"value": "Target distance"
},
"0x0075": {
"attribute_name": "engineering_units",
"value": 31
}
},
"unsupported_attributes": {}
},
"0x000d": {
"endpoint_attribute": "analog_output",
"attributes": {
"0x001c": {
"attribute_name": "description",
"value": "Sensitivity"
},
"0x0041": {
"attribute_name": "max_present_value",
"value": 9
},
"0x0045": {
"attribute_name": "min_present_value",
"value": 1
},
"0x006a": {
"attribute_name": "resolution",
"value": 1
}
},
"unsupported_attributes": {}
}
},
"out_clusters": {
"0x000a": {
"endpoint_attribute": "time",
"attributes": {},
"unsupported_attributes": {}
},
"0x0019": {
"endpoint_attribute": "ota",
"attributes": {
"0x0002": {
"attribute_name": "current_file_version",
"value": 74
}
},
"unsupported_attributes": {}
}
}
},
"2": {
"device_type": {
"name": "COMBINED_INTERFACE",
"id": 7
},
"profile_id": 260,
"in_clusters": {
"0x000d": {
"endpoint_attribute": "analog_output",
"attributes": {
"0x001c": {
"attribute_name": "description",
"value": "Min range"
},
"0x0075": {
"attribute_name": "engineering_units",
"value": 118
},
"0x0041": {
"attribute_name": "max_present_value",
"value": 950
},
"0x0045": {
"attribute_name": "min_present_value",
"value": 0
},
"0x006a": {
"attribute_name": "resolution",
"value": 10
}
},
"unsupported_attributes": {}
}
},
"out_clusters": {}
},
"3": {
"device_type": {
"name": "COMBINED_INTERFACE",
"id": 7
},
"profile_id": 260,
"in_clusters": {
"0x000d": {
"endpoint_attribute": "analog_output",
"attributes": {
"0x001c": {
"attribute_name": "description",
"value": "Max range"
},
"0x0075": {
"attribute_name": "engineering_units",
"value": 118
},
"0x0041": {
"attribute_name": "max_present_value",
"value": 950
},
"0x0045": {
"attribute_name": "min_present_value",
"value": 10
},
"0x006a": {
"attribute_name": "resolution",
"value": 10
}
},
"unsupported_attributes": {}
}
},
"out_clusters": {}
},
"4": {
"device_type": {
"name": "COMBINED_INTERFACE",
"id": 7
},
"profile_id": 260,
"in_clusters": {
"0x000d": {
"endpoint_attribute": "analog_output",
"attributes": {
"0x001c": {
"attribute_name": "description",
"value": "Detection delay"
},
"0x0075": {
"attribute_name": "engineering_units",
"value": 159
},
"0x0041": {
"attribute_name": "max_present_value",
"value": 20000
},
"0x0045": {
"attribute_name": "min_present_value",
"value": 0
},
"0x0055": {
"attribute_name": "present_value",
"value": 0
},
"0x006a": {
"attribute_name": "resolution",
"value": 100
}
},
"unsupported_attributes": {}
}
},
"out_clusters": {}
},
"5": {
"device_type": {
"name": "COMBINED_INTERFACE",
"id": 7
},
"profile_id": 260,
"in_clusters": {
"0x000d": {
"endpoint_attribute": "analog_output",
"attributes": {
"0x001c": {
"attribute_name": "description",
"value": "Fading time"
},
"0x0075": {
"attribute_name": "engineering_units",
"value": 159
},
"0x0041": {
"attribute_name": "max_present_value",
"value": 200000
},
"0x0045": {
"attribute_name": "min_present_value",
"value": 2000
},
"0x006a": {
"attribute_name": "resolution",
"value": 1000
}
},
"unsupported_attributes": {}
}
},
"out_clusters": {}
},
"242": {
"device_type": {
"name": "PROXY_BASIC",
"id": 97
},
"profile_id": 41440,
"in_clusters": {},
"out_clusters": {
"0x0021": {
"endpoint_attribute": "green_power",
"attributes": {},
"unsupported_attributes": {}
}
}
}
}
}
}

Logs

Logs
[Paste the logs here]

Custom quirk

Custom quirk

"""Tuya mmw radar occupancy sensor."""
##2457
import math
from typing import Dict

from zigpy.profiles import zgp, zha
from zigpy.quirks import CustomDevice
import zigpy.types as t
from zigpy.zcl.clusters.general import (
AnalogInput,
AnalogOutput,
Basic,
GreenPowerProxy,
Groups,
Ota,
Scenes,
Time,
)
from zigpy.zcl.clusters.measurement import IlluminanceMeasurement, OccupancySensing

from zhaquirks.const import (
DEVICE_TYPE,
ENDPOINTS,
INPUT_CLUSTERS,
MODELS_INFO,
OUTPUT_CLUSTERS,
PROFILE_ID,
)
from zhaquirks.tuya import NoManufacturerCluster, TuyaLocalCluster, TuyaNewManufCluster
from zhaquirks.tuya.mcu import (
DPToAttributeMapping,
TuyaAttributesCluster,
TuyaMCUCluster,
)

ZONE_TYPE = 0x0001

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 TuyaMmwRadarSensitivity(TuyaAttributesCluster, AnalogOutput):
"""AnalogOutput cluster for sensitivity."""

def __init__(self, *args, **kwargs):
    """Init."""
    super().__init__(*args, **kwargs)
    self._update_attribute(self.attributes_by_name["description"].id, "Sensitivity")
    self._update_attribute(self.attributes_by_name["min_present_value"].id, 1)
    self._update_attribute(self.attributes_by_name["max_present_value"].id, 9)
    self._update_attribute(self.attributes_by_name["resolution"].id, 1)

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

def __init__(self, *args, **kwargs):
    """Init."""
    super().__init__(*args, **kwargs)
    self._update_attribute(self.attributes_by_name["description"].id, "Min range")
    self._update_attribute(self.attributes_by_name["min_present_value"].id, 0)
    self._update_attribute(self.attributes_by_name["max_present_value"].id, 950)
    self._update_attribute(self.attributes_by_name["resolution"].id, 10)
    self._update_attribute(
        self.attributes_by_name["engineering_units"].id, 118
    )  # 31: meters

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

def __init__(self, *args, **kwargs):
    """Init."""
    super().__init__(*args, **kwargs)
    self._update_attribute(self.attributes_by_name["description"].id, "Max range")
    self._update_attribute(self.attributes_by_name["min_present_value"].id, 10)
    self._update_attribute(self.attributes_by_name["max_present_value"].id, 950)
    self._update_attribute(self.attributes_by_name["resolution"].id, 10)
    self._update_attribute(
        self.attributes_by_name["engineering_units"].id, 118
    )  # 31: meters

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

def __init__(self, *args, **kwargs):
    """Init."""
    super().__init__(*args, **kwargs)
    self._update_attribute(
        self.attributes_by_name["description"].id, "Detection delay"
    )
    self._update_attribute(self.attributes_by_name["min_present_value"].id, 000)
    self._update_attribute(self.attributes_by_name["max_present_value"].id, 20000)
    self._update_attribute(self.attributes_by_name["resolution"].id, 100)
    self._update_attribute(
        self.attributes_by_name["engineering_units"].id, 159
    )  # 73: seconds

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

def __init__(self, *args, **kwargs):
    """Init."""
    super().__init__(*args, **kwargs)
    self._update_attribute(self.attributes_by_name["description"].id, "Fading time")
    self._update_attribute(self.attributes_by_name["min_present_value"].id, 2000)
    self._update_attribute(self.attributes_by_name["max_present_value"].id, 200000)
    self._update_attribute(self.attributes_by_name["resolution"].id, 1000)
    self._update_attribute(
        self.attributes_by_name["engineering_units"].id, 159
    )  # 73: seconds

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

def __init__(self, *args, **kwargs):
    """Init."""
    super().__init__(*args, **kwargs)
    self._update_attribute(
        self.attributes_by_name["description"].id, "Target distance"
    )
    self._update_attribute(
        self.attributes_by_name["engineering_units"].id, 31
    )  # 31: meters

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 TuyaMmwRadarClusterVariant1(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 TuyaMmwRadarOccupancyVariant1(CustomDevice):
"""Millimeter wave occupancy sensor, variant 1."""

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"),
        ("_TZE204_qasjif9e", "TS0601"),
        ("_TZE204_dtzziy1e", "TS0601"),
        ("_TZE204_sbyx0lm6", "TS0601"),
        ("_TZE204_ex3rcdha", "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,
                TuyaMmwRadarClusterVariant1,
                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 TuyaMmwRadarOccupancyVariant1GPP(CustomDevice):
"""Millimeter wave occupancy sensor, variant 1 with GPP."""

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"),
        ("_TZE204_qasjif9e", "TS0601"),
        ("_TZE204_sbyx0lm6", "TS0601"),
        ("_TZE204_ex3rcdha", "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,
                TuyaMmwRadarClusterVariant1,
                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 TuyaMmwRadarClusterVariant2(TuyaMmwRadarClusterBase):
"""Tuya MMW radar cluster, variant 2."""

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 TuyaMmwRadarOccupancyVariant2(CustomDevice):
"""Millimeter wave occupancy sensor, variant 2."""

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_bmdsp6bs", "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,
                TuyaMmwRadarClusterVariant2,
                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],
        },
    }
}

Additional information

No response

@TheJulianJES TheJulianJES added the Tuya Request/PR regarding a Tuya device label Aug 24, 2024
@dermaxxx213
Copy link

Sorry for my question, but works the sensor with this quirk?
must i copy the hole code or only a part?
i try both, but it didnt work.

@agf102
Copy link
Author

agf102 commented Sep 2, 2024

Sorry for my question, but works the sensor with this quirk? must i copy the hole code or only a part? i try both, but it didnt work.

Using the whole quirk included in this post I was able to get partial functionality but the illuminance and occupancy sensors don't work and I can't therefore test whether the controls are working either

@fabianteunissen
Copy link

Do you know what datapoints it uses?

Koenkk/zigbee2mqtt#23705 (comment)?

@lajoshanko
Copy link

Seems to be the z2mqtt version is working, maybe it can speed up the zha support too:
Koenkk/zigbee2mqtt#23705 (comment)

@przemek-baranowski
Copy link

signature in ZHA looks like this:

{ "node_descriptor": { "logical_type": 1, "complex_descriptor_available": 0, "user_descriptor_available": 0, "reserved": 0, "aps_flags": 0, "frequency_band": 8, "mac_capability_flags": 142, "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", "0xef00" ], "output_clusters": [ "0x000a", "0x0019" ] }, "242": { "profile_id": "0xa1e0", "device_type": "0x0061", "input_clusters": [], "output_clusters": [ "0x0021" ] } }, "manufacturer": "_TZE204_ex3rcdha", "model": "TS0601", "class": "zigpy.device.Device" }

@esrfkos
Copy link

esrfkos commented Oct 17, 2024

Hey Guys is there any update on this issue?
Would be wonderful if someone could share their progress.

@esrfkos
Copy link

esrfkos commented Oct 17, 2024

Here is a custom quirk i tried to put together with chatGPT:

`from zigpy.quirks import CustomDevice
from zigpy.zcl.clusters.general import Basic, Groups, Scenes, Ota, Time
from zhaquirks.tuya import TuyaManufClusterAttributes, TuyaMCUCluster

class TuyaCustomCluster(TuyaMCUCluster):
"""Tuya Manufacturer-specific cluster for 0xEF00."""

# This handles attribute reporting and command mapping for the Tuya cluster
attributes = {
    0x0000: ('tuya_attribute_1', 'Type', bool),
    0x0001: ('tuya_attribute_2', 'Type', int),
}
client_commands = {}
server_commands = {}

class TZE204EX3RCDHA(CustomDevice):
"""Custom device representing Tuya-based TS0601 device."""

signature = {
    "endpoints": {
        1: {
            "profile_id": 0x0104,
            "device_type": 0x0051,
            "input_clusters": [
                Basic.cluster_id,
                Groups.cluster_id,
                Scenes.cluster_id,
                TuyaCustomCluster.cluster_id,
            ],
            "output_clusters": [
                Time.cluster_id,
                Ota.cluster_id,
            ],
        }
    },
    "manufacturer": "_TZE204_ex3rcdha",
    "model": "TS0601",
}

replacement = {
    "endpoints": {
        1: {
            "profile_id": 0x0104,
            "device_type": 0x0051,
            "input_clusters": [
                Basic.cluster_id,
                Groups.cluster_id,
                Scenes.cluster_id,
                TuyaCustomCluster,
            ],
            "output_clusters": [
                Time.cluster_id,
                Ota.cluster_id,
            ],
        }
    }
}

`

@tonyhollaar
Copy link

I have not got your script @esrfkos working. I have found the below script which at least shows in Home Assistant the correct buttons but does not actually work for this motion sensor. Is there anybody that can solve this e.g. find the correct data_point_handlers and fix the code?

"""BlitzWolf IS-3/Tuya motion rechargeable occupancy sensor."""
import math
from typing import Dict, Optional, Tuple, Union

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.zcl.clusters.measurement import (
    IlluminanceMeasurement,
    OccupancySensing,
    RelativeHumidity,
    TemperatureMeasurement,
)
from zigpy.zcl.clusters.security import IasZone

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,
)
from zhaquirks.tuya.mcu import (
    DPToAttributeMapping,
    TuyaAttributesCluster,
    TuyaMCUCluster,
)

ZONE_TYPE = 0x0001


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 TuyaMmwRadarClusterV4(TuyaMmwRadarClusterBase):
    """Tuya MMW radar cluster, variant 4."""

    dp_to_attribute: Dict[int, DPToAttributeMapping] = {
        1: DPToAttributeMapping(
            TuyaOccupancySensing.ep_attribute,
            "occupancy",
        ),
        12: DPToAttributeMapping(
            TuyaMmwRadarFadingTime.ep_attribute,
            "fading_time",
            converter=lambda x: x * 100,
            dp_converter=lambda x: x // 100,
            endpoint_id=12,
        ),
        19: DPToAttributeMapping(
            TuyaMmwRadarTargetDistance.ep_attribute,
            "target_distance",
            endpoint_id=19,
        ),
        20: DPToAttributeMapping(
            TuyaIlluminanceMeasurement.ep_attribute,
            "illuminance",
            converter=lambda x: int(math.log10(x) * 10000 + 1) if x > 0 else int(0),
            endpoint_id=20,
        ),
        101: DPToAttributeMapping(
            TuyaMmwRadarSensitivity.ep_attribute,
            "sensitivity",
            endpoint_id=101,
        ),
        102: DPToAttributeMapping(
            TuyaMmwRadarDetectionDelay.ep_attribute,
            "detection_delay",
            converter=lambda x: x * 10,
            dp_converter=lambda x: x // 10,
            endpoint_id=102,
        ),
        111: DPToAttributeMapping(
            TuyaMmwRadarMinRange.ep_attribute,
            "min_range",
            endpoint_id=111,
        ),
        112: DPToAttributeMapping(
            TuyaMmwRadarMaxRange.ep_attribute,
            "max_range",
            endpoint_id=112,
        ),
    }

    data_point_handlers = {
        1: "_dp_2_attr_update",
        12: "_dp_2_attr_update",
        19: "_dp_2_attr_update",
        20: "_dp_2_attr_update",
        101: "_dp_2_attr_update",
        102: "_dp_2_attr_update",
        111: "_dp_2_attr_update",
        112: "_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"),
            #("_TZE204_ex3rcdha", "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 THIS IS PARTIALLY WORKING"""

    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_ex3rcdha", "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"),
            #("_TZE204_ex3rcdha", "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],
            },
        }
    }

class TuyaMmwRadarOccupancyVariant4(CustomDevice):
    """Mini/Ceiling Human Breathe Sensor by danandrea737"""

    signature = {
        #  endpoint=1, profile=260, device_type=81, device_version=1,
        #  input_clusters=[0, 4, 5, 61184], output_clusters=[25, 10])
        MODELS_INFO: [
            ("_TZE204_mhxn2jso", "TS0601"),
            #("_TZE204_ex3rcdha", "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,
                    TuyaMmwRadarClusterV4,
                    TuyaOccupancySensing,
                    TuyaIlluminanceMeasurement,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            12: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
                INPUT_CLUSTERS: [
                    TuyaMmwRadarFadingTime,
                ],
                OUTPUT_CLUSTERS: [],
            },
            19: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
                INPUT_CLUSTERS: [
                    TuyaMmwRadarTargetDistance,
                ],
                OUTPUT_CLUSTERS: [],
            },
            101: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
                INPUT_CLUSTERS: [
                    TuyaMmwRadarSensitivity,
                ],
                OUTPUT_CLUSTERS: [],
            },
            102: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
                INPUT_CLUSTERS: [
                    TuyaMmwRadarDetectionDelay,
                ],
                OUTPUT_CLUSTERS: [],
            },
            111: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
                INPUT_CLUSTERS: [
                    TuyaMmwRadarMinRange,
                ],
                OUTPUT_CLUSTERS: [],
            },
            112: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
                INPUT_CLUSTERS: [
                    TuyaMmwRadarMaxRange,
                ],
                OUTPUT_CLUSTERS: [],
            },
            242: {
                PROFILE_ID: zgp.PROFILE_ID,
                DEVICE_TYPE: zgp.DeviceType.PROXY_BASIC,
                INPUT_CLUSTERS: [],
                OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
            },
        }
    }

@wesleysee
Copy link

Here's a custom quirk I put together that seems to be working as far as I can tell.

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

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,
    MultistateInput,
)
from zigpy.zcl.clusters.measurement import (
    IlluminanceMeasurement,
    OccupancySensing
)
from zigpy.zcl.clusters.security import IasZone

from zhaquirks import Bus, LocalDataCluster, MotionOnEvent
from zhaquirks.const import (
    DEVICE_TYPE,
    ENDPOINTS,
    INPUT_CLUSTERS,
    MODEL,
    MOTION_EVENT,
    OUTPUT_CLUSTERS,
    PROFILE_ID,
)

from zhaquirks.tuya import (
    NoManufacturerCluster,
    TuyaLocalCluster,
    TuyaNewManufCluster,
)
from zhaquirks.tuya.mcu import (
    # TuyaDPType,
    DPToAttributeMapping,
    TuyaAttributesCluster,
    TuyaMCUCluster,
)


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 TuyaIlluminanceMeasurement(IlluminanceMeasurement, TuyaLocalCluster):
    """Tuya local IlluminanceMeasurement cluster."""

class TuyaMmwPresenceState (TuyaAttributesCluster, MultistateInput):
    """Tuya local AnalogInput cluster."""

    def __init__(self, *args, **kwargs):
        """Init."""
        super().__init__(*args, **kwargs)
        self._update_attribute(self.attributes_by_name["description"].id, "presence_state")
        self._update_attribute(self.attributes_by_name["number_of_states"].id, 2)
        self._update_attribute(self.attributes_by_name["state_text"].id, ["Presence","None"])

class TuyaPresenceState(t.enum8):
    PRESENCE = 0
    NONE = 1

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

    def __init__(self, *args, **kwargs):
        """Init."""
        super().__init__(*args, **kwargs)
        self._update_attribute(self.attributes_by_name["description"].id, "fading_time")
        self._update_attribute(self.attributes_by_name["min_present_value"].id, 30)
        self._update_attribute(self.attributes_by_name["max_present_value"].id, 180)
        self._update_attribute(self.attributes_by_name["resolution"].id, 1)
        self._update_attribute(
            self.attributes_by_name["engineering_units"].id, 73
        )

class TuyaMmwRadarMotionDetectionSensitivity(TuyaAttributesCluster, AnalogOutput):
    """AnalogOutput cluster for Large motion detection sensitivity."""

    def __init__(self, *args, **kwargs):
        """Init."""
        super().__init__(*args, **kwargs)
        self._update_attribute(self.attributes_by_name["description"].id, "Motion Sensitivity")
        self._update_attribute(self.attributes_by_name["min_present_value"].id, 0)
        self._update_attribute(self.attributes_by_name["max_present_value"].id, 10)
        self._update_attribute(self.attributes_by_name["resolution"].id, 1)

class TuyaMmwRadarMotionMinRange(TuyaAttributesCluster, AnalogOutput):
    """AnalogOutput cluster for min range."""
                                     
    def __init__(self, *args, **kwargs):                           
        """Init."""             
        super().__init__(*args, **kwargs)           
        self._update_attribute(            
            self.attributes_by_name["description"].id, "Motion Min range"
        )                        
        self._update_attribute(self.attributes_by_name["min_present_value"].id, 0)
        self._update_attribute(self.attributes_by_name["max_present_value"].id, 600)
        self._update_attribute(self.attributes_by_name["resolution"].id, 10)
        self._update_attribute(         
            self.attributes_by_name["engineering_units"].id, 118
        )  # 31: meters 

class TuyaMmwRadarMotionMaxRange(TuyaAttributesCluster, AnalogOutput):           
    """AnalogOutput cluster for max range."""       
                                                                           
    def __init__(self, *args, **kwargs):       
        """Init."""                                                            
        super().__init__(*args, **kwargs)                 
        self._update_attribute(                                
            self.attributes_by_name["description"].id, "Motion Max range"
        )                                      
        self._update_attribute(self.attributes_by_name["min_present_value"].id, 0)
        self._update_attribute(self.attributes_by_name["max_present_value"].id, 600)
        self._update_attribute(self.attributes_by_name["resolution"].id, 10)
        self._update_attribute(             
            self.attributes_by_name["engineering_units"].id, 118                  
        )  # 31: meters


class TuyaMmwRadarBreathMotionDetectionSensitivity(TuyaAttributesCluster, AnalogOutput):
    """AnalogOutput cluster for Small motion detection sensitivity."""

    def __init__(self, *args, **kwargs):
        """Init."""
        super().__init__(*args, **kwargs)
        self._update_attribute(self.attributes_by_name["description"].id, "Breath-Motion Sensitivity")
        self._update_attribute(self.attributes_by_name["min_present_value"].id, 0)
        self._update_attribute(self.attributes_by_name["max_present_value"].id, 10)
        self._update_attribute(self.attributes_by_name["resolution"].id, 1)


class TuyaMmwRadarBreathMotionMinRange(TuyaAttributesCluster, AnalogOutput):
    """AnalogOutput cluster for min range."""
                                     
    def __init__(self, *args, **kwargs):                           
        """Init."""             
        super().__init__(*args, **kwargs)           
        self._update_attribute(            
            self.attributes_by_name["description"].id, "Breath-Motion Min range"
        )                        
        self._update_attribute(self.attributes_by_name["min_present_value"].id, 0)
        self._update_attribute(self.attributes_by_name["max_present_value"].id, 600)
        self._update_attribute(self.attributes_by_name["resolution"].id, 10)
        self._update_attribute(         
            self.attributes_by_name["engineering_units"].id, 118
        )  # 31: meters 

class TuyaMmwRadarBreathMotionMaxRange(TuyaAttributesCluster, AnalogOutput):           
    """AnalogOutput cluster for max range."""       
                                                                           
    def __init__(self, *args, **kwargs):       
        """Init."""                                                            
        super().__init__(*args, **kwargs)                 
        self._update_attribute(                                
            self.attributes_by_name["description"].id, "Breath-Motion Max range"
        )                                      
        self._update_attribute(self.attributes_by_name["min_present_value"].id, 0)
        self._update_attribute(self.attributes_by_name["max_present_value"].id, 600)
        self._update_attribute(self.attributes_by_name["resolution"].id, 10)
        self._update_attribute(             
            self.attributes_by_name["engineering_units"].id, 118                  
        )  # 31: meters


class TuyaMmwRadarCluster(NoManufacturerCluster, TuyaMCUCluster):
    """Mmw radar cluster."""
    attributes = TuyaMCUCluster.attributes.copy()
    attributes.update(
        {
            # random attribute IDs
            0xEF01: ("occupancy", t.uint32_t, True),
            0xEF05: ("presence_state", TuyaPresenceState, True),
            0xEF66: ("fading_time", t.uint32_t, True),
            0xEF6A: ("illuminance", t.uint32_t, True),
            0xEF02: ("motion_sensitivity", t.uint32_t, True),
            0xEF03: ("motion_min_range", t.uint32_t, True),
            0xEF04: ("motion_max_range", t.uint32_t, True),
            0xEF6B: ("breath_sensitivity", t.uint32_t, True),
            0xEF6C: ("breath_min_range", t.uint32_t, True),
            0xEF6D: ("breath_max_range", t.uint32_t, True),
        }
    )

    dp_to_attribute: Dict[int, DPToAttributeMapping] = {
        12: DPToAttributeMapping(
            TuyaIlluminanceMeasurement.ep_attribute,
            "measured_value",
            converter=lambda x: int(math.log10(x) * 10000 + 1) if x > 0 else int(1),
        ),
        101: DPToAttributeMapping(
            TuyaOccupancySensing.ep_attribute,
            "occupancy",
            converter=lambda x: True if x == 0 else False,
        ),
        104: DPToAttributeMapping(
            TuyaMmwRadarFadingTime.ep_attribute,
            "present_value",
            endpoint_id=2,
        ),
        105: DPToAttributeMapping(
            TuyaMmwRadarMotionDetectionSensitivity.ep_attribute,
            "present_value",
            endpoint_id=3,
        ),
        107: DPToAttributeMapping(
            TuyaMmwRadarBreathMotionDetectionSensitivity.ep_attribute,
            "present_value",
            endpoint_id=4,
        ),
        109: DPToAttributeMapping(                                           
            TuyaMmwRadarMotionMaxRange.ep_attribute,      
            "present_value",                                               
            endpoint_id=5,
        ),                                                     
        110: DPToAttributeMapping(                                  
            TuyaMmwRadarMotionMinRange.ep_attribute, 
            "present_value",                                                       
            endpoint_id=6,
        ),
        111: DPToAttributeMapping(                                           
            TuyaMmwRadarBreathMotionMaxRange.ep_attribute,      
            "present_value",                                               
            endpoint_id=7,           
        ),                                                     
        112: DPToAttributeMapping(                                  
            TuyaMmwRadarBreathMotionMinRange.ep_attribute, 
            "present_value",                                                       
            endpoint_id=8,
        ),
    }

    data_point_handlers = {
        1: "_dp_2_attr_update",
        12: "_dp_2_attr_update",
        101: "_dp_2_attr_update",
        104: "_dp_2_attr_update",
        105: "_dp_2_attr_update",
        107: "_dp_2_attr_update",
        109: "_dp_2_attr_update",
        110: "_dp_2_attr_update",
        111: "_dp_2_attr_update",
        112: "_dp_2_attr_update",
    }

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

    signature = {
        "models_info": [
            ("_TZE204_ex3rcdha", "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: {
                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,
                    TuyaMmwRadarCluster,
                    TuyaIlluminanceMeasurement,
                    TuyaOccupancySensing,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            2: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
                INPUT_CLUSTERS: [
                    TuyaMmwRadarFadingTime,
                ],
                OUTPUT_CLUSTERS: [],
            },
            3: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
                INPUT_CLUSTERS: [
                    TuyaMmwRadarMotionDetectionSensitivity,
                ],
                OUTPUT_CLUSTERS: [],
            },
            4: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
                INPUT_CLUSTERS: [
                    TuyaMmwRadarBreathMotionDetectionSensitivity,
                ],
                OUTPUT_CLUSTERS: [],
            },
            5: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
                INPUT_CLUSTERS: [
                    TuyaMmwRadarMotionMaxRange,
                ],
                OUTPUT_CLUSTERS: [],
            },
            6: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
                INPUT_CLUSTERS: [
                    TuyaMmwRadarMotionMinRange,
                ],
                OUTPUT_CLUSTERS: [],
            },
            7: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
                INPUT_CLUSTERS: [
                    TuyaMmwRadarBreathMotionMaxRange,
                ],
                OUTPUT_CLUSTERS: [],
            },
            8: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
                INPUT_CLUSTERS: [
                    TuyaMmwRadarBreathMotionMinRange,
                ],
                OUTPUT_CLUSTERS: [],
            },
            242: {                                    
                PROFILE_ID: 41440,                                                
                DEVICE_TYPE: 97,                                                      
                INPUT_CLUSTERS: [],                                        
                OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],                     
            }, 
        }
    }

@lajoshanko
Copy link

For me it does not work the occupancy always clear no matter what

@mdshw5
Copy link

mdshw5 commented Nov 15, 2024

@wesleysee That quirk works great for some of these devices that I just received today! Thanks for sharing and helping me get started with these cheap zigbee devices!

@tonyhollaar
Copy link

@wesleysee works as well for me! Thank you so much...

@lajoshanko
Copy link

Update: the light sensor works without a problem, the occupancy seemingly works sometime, but cannot modifie any of the provided values:
IMG_9793

@wesleysee
Copy link

wesleysee commented Nov 17, 2024

The above error seem to be a more widespread issue, I'm seeing it across a variety of my Tuya devices. Also see it reported in this issue: home-assistant/core#130218

@lajoshanko
Copy link

After adding it a few times the occupancy sensor also started to work. Thanks fornthe quirk!

@sportluffi
Copy link

The above error seem to be a more widespread issue, I'm seeing it across a variety of my Tuya devices. Also see it reported in this issue: home-assistant/core#130218

This issue has been merged and released. It is now possible to change the fading time and the Motion max range from the HA UI.

@Madmorgan007
Copy link

Hi I'm new to home assistant so how do I add custom quirk's ???

@tonyhollaar
Copy link

Hi I'm new to home assistant so how do I add custom quirk's ???

  1. Open your folder of home assistant, i use the seperately downloaded and installed add-on Samba to access my files from my PC to access my home assistant yellow drive/folder.
  2. Make a new folder in your home assistant directory. In my case the folder is named “custom_zha_quirks”;
  3. Make a new file named e.g. MYFILE.PY
    (Can be any filename)

Paste the code from this forum of the custom quirk into it.

  1. Open configuration. yaml with a text editor e.g. notepad++
    add the following lines:
    zha:
    enable_quirks: true
    custom_quirks_path: /config/
    custom_zha_quirks
  2. Restart home assistant from Settings menu
  3. remove and re-add your device in home assistant as an e.g. zigbee device and then press the black button on your human presence sensor untill it blinks
  4. verify it works and use an automation to turn on/off the light

@Madmorgan007
Copy link

@tonyhollaar Thank you.

@Madmorgan007
Copy link

Hello again, The script has worked and showing more settings now but the device's light is aways off and the occupancy shows as clear and does not change.

@TheJulianJES TheJulianJES added the device support request This requests support for a new device label Jan 26, 2025
@prairiesnpr prairiesnpr linked a pull request Feb 17, 2025 that will close this issue
3 tasks
@Rob4ik77
Copy link

@wesleysee You are amazing! thank you mate! works like a charm

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

Successfully merging a pull request may close this issue.