-
-
Save rabin-io/a279f93903a3574aedc0c4d7f860febc to your computer and use it in GitHub Desktop.
"""Tuya mmw radar occupancy sensor.""" | |
import math | |
from typing import Dict, Optional, Tuple, Union | |
from zigpy.profiles import zha | |
from zigpy.quirks import CustomDevice | |
import zigpy.types as t | |
from zigpy.zcl import foundation | |
from zigpy.zcl.clusters.general import ( | |
AnalogInput, | |
AnalogOutput, | |
Basic, | |
GreenPowerProxy, | |
Groups, | |
Identify, | |
Ota, | |
Scenes, | |
Time, | |
) | |
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 ( | |
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 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, 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 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, 100) | |
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, 1000) | |
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 TuyaMmwRadarCluster(NoManufacturerCluster, TuyaMCUCluster): | |
"""Mmw radar cluster.""" | |
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), | |
} | |
) | |
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, | |
# converter=lambda x: x / 100, | |
# dp_converter=lambda x: x * 100, | |
), | |
4: DPToAttributeMapping( | |
TuyaMmwRadarMaxRange.ep_attribute, | |
"present_value", | |
endpoint_id=3, | |
# converter=lambda x: x / 100, | |
# dp_converter=lambda x: x * 100, | |
), | |
6: DPToAttributeMapping( | |
TuyaMCUCluster.ep_attribute, | |
"self_test", | |
), | |
9: DPToAttributeMapping( | |
TuyaMmwRadarTargetDistance.ep_attribute, | |
"present_value", | |
# converter=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: 10000 * math.log10(x) + 1 if x != 0 else 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 TuyaMmwRadarOccupancy(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_ikvncluo", "TS0601"), | |
("_TZE204_qasjif9e", "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, | |
TuyaMmwRadarCluster, | |
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: [], | |
}, | |
} | |
} |
Has anyone got it going with this one "_TZE204_sooucan5", "TS0601" ? i tried adding the line in and also moving it up the list but it didnt work
thanks
got a new model
TS0601
by _TZE204_kyhbrfyl
Zigbee info
IEEE: a4:c1:38:86:19:73:7e:2d
Nwk: 0x3980
Device Type: Router
LQI: 61
RSSI: Unknown
Last seen: 2024-06-27T21:20:59
Power source: Mains
@eduardkirkosa does it work by just adding it to the models_info
list? (after you clear the cache and reload)
i tried to some tutorial how to load the quirks, but for sure i do something wrong and it's not working
i added in /homeassistant/configuration.yaml
zha:
enable_quirks: true
custom_quirks_path: /config/custom_zha_quirks/
created a file there /homeassistant/config/custom_zha_quirks/ts0601_radar.py
and nothing , 3 reboots :(
This is all I had to do in my HA main config,
zha:
custom_quirks_path: /config/custom_zha_quirks/
And on the filesystem it looks like this (ignore the pycache folder)
➜ ~ ls -lR /usr/share/hassio/homeassistant/custom_zha_quirks/
/usr/share/hassio/homeassistant/custom_zha_quirks/:
total 16
drwxr-xr-x 2 root root 4096 May 1 19:08 __pycache__
-rw-r--r-- 1 root root 10991 May 1 19:07 ts0601_radar.py
/usr/share/hassio/homeassistant/custom_zha_quirks/__pycache__:
total 16
-rw-r--r-- 1 root root 12563 May 1 19:08 ts0601_radar.cpython-312.pyc
checked the logs, i see this:
Log details (WARNING)
Logger: zhaquirks
Source: /usr/local/lib/python3.12/site-packages/zhaquirks/init.py:465
First occurred: 11:12:38 PM (1 occurrences)
Last logged: 11:12:38 PM
Loaded custom quirks. Please contribute them to https://github.com/zigpy/zha-device-handlers
I don't know, try removing the device from ZHA/HA and delete the cache folder __pycache__
and restart HA. And then try to re-pair it again.
PS, this is not a support forum, so you get a very limited exposure and support, try HA zigbee forum.
it's ok, will troubleshoot more in the weekend, thanks a lot for the help and time :)
@frasergr, I added the device model to the list
thx @dolnet for the reminder.