diff --git a/zhaquirks/quirk_ids.py b/zhaquirks/quirk_ids.py index bb64e006e9..35b2aa0ff3 100644 --- a/zhaquirks/quirk_ids.py +++ b/zhaquirks/quirk_ids.py @@ -6,6 +6,7 @@ # Tuya TUYA_PLUG_ONOFF = "tuya.plug_on_off_attributes" # plugs with configurable attributes on the OnOff cluster TUYA_PLUG_MANUFACTURER = "tuya.plug_manufacturer_attributes" # plugs with configurable attributes on a custom cluster +TUYA_MMWRADAR = "tuya.mmw_radar" # mmw radar devices # Xiaomi XIAOMI_AQARA_VIBRATION_AQ1 = ( diff --git a/zhaquirks/tuya/ts0601_motion.py b/zhaquirks/tuya/ts0601_motion.py index 45c34e8a23..0836e5f83c 100644 --- a/zhaquirks/tuya/ts0601_motion.py +++ b/zhaquirks/tuya/ts0601_motion.py @@ -9,6 +9,7 @@ from zigpy.zcl import foundation from zigpy.zcl.clusters.general import ( AnalogInput, + AnalogOutput, Basic, GreenPowerProxy, Groups, @@ -35,17 +36,33 @@ OUTPUT_CLUSTERS, PROFILE_ID, ) +from zhaquirks.quirk_ids import TUYA_MMWRADAR from zhaquirks.tuya import ( - DPToAttributeMapping, + NoManufacturerCluster, TuyaLocalCluster, TuyaManufCluster, TuyaNewManufCluster, ) -from zhaquirks.tuya.mcu import TuyaMCUCluster +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.""" @@ -66,6 +83,74 @@ 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.""" @@ -115,99 +200,141 @@ class NeoMotionManufCluster(TuyaNewManufCluster): } -class MmwRadarManufCluster(TuyaMCUCluster): - """Neo manufacturer cluster.""" - - # # Possible DPs and values - # presence_state: presence - # target distance: 1.61m - # illuminance: 250lux - # sensitivity: 9 - # minimum_detection_distance: 0.00m - # maximum_detection_distance: 4.05m - # dp_detection_delay: 0.1 - # dp_fading_time: 5.0 - # ¿illuminance?: 255lux - # presence_brightness: no control - # no_one_brightness: no control - # current_brightness: off +class TuyaMmwRadarClusterBase(NoManufacturerCluster, TuyaMCUCluster): + """Mmw radar cluster, base class.""" attributes = TuyaMCUCluster.attributes.copy() attributes.update( { # ramdom attribute IDs - 0xEF02: ("dp_2", t.uint32_t, True), - 0xEF03: ("dp_3", t.uint32_t, True), - 0xEF04: ("dp_4", t.uint32_t, True), - 0xEF06: ("dp_6", t.enum8, True), - 0xEF65: ("dp_101", t.uint32_t, True), - 0xEF66: ("dp_102", t.uint32_t, True), - 0xEF67: ("dp_103", t.CharacterString, True), - 0xEF69: ("dp_105", t.enum8, True), - 0xEF6A: ("dp_106", t.enum8, True), - 0xEF6B: ("dp_107", t.enum8, True), - 0xEF6C: ("dp_108", t.uint32_t, True), + 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( - TuyaMCUCluster.ep_attribute, - "dp_2", + TuyaMmwRadarSensitivity.ep_attribute, + "present_value", ), 3: DPToAttributeMapping( - TuyaMCUCluster.ep_attribute, - "dp_3", + TuyaMmwRadarMinRange.ep_attribute, + "present_value", + endpoint_id=2, ), 4: DPToAttributeMapping( - TuyaMCUCluster.ep_attribute, - "dp_4", + TuyaMmwRadarMaxRange.ep_attribute, + "present_value", + endpoint_id=3, ), 6: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, - "dp_6", + "self_test", ), 9: DPToAttributeMapping( - TuyaAnalogInput.ep_attribute, + TuyaMmwRadarTargetDistance.ep_attribute, "present_value", lambda x: x / 100, ), 101: DPToAttributeMapping( - TuyaMCUCluster.ep_attribute, - "dp_101", + TuyaMmwRadarDetectionDelay.ep_attribute, + "present_value", + converter=lambda x: x * 100, + dp_converter=lambda x: x // 100, + endpoint_id=4, ), 102: DPToAttributeMapping( - TuyaMCUCluster.ep_attribute, - "dp_102", + TuyaMmwRadarFadingTime.ep_attribute, + "present_value", + converter=lambda x: x * 100, + dp_converter=lambda x: x // 100, + endpoint_id=5, ), 103: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, - "dp_103", + "cli", ), 104: DPToAttributeMapping( TuyaIlluminanceMeasurement.ep_attribute, "measured_value", - lambda x: 10000 * math.log10(x) + 1 if x != 0 else 0, + converter=lambda x: int(math.log10(x) * 10000 + 1) if x > 0 else int(0), ), - 105: DPToAttributeMapping( - TuyaMCUCluster.ep_attribute, - "dp_105", + } + + 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", ), - 106: DPToAttributeMapping( - TuyaMCUCluster.ep_attribute, - "dp_106", + 2: DPToAttributeMapping( + TuyaMmwRadarSensitivity.ep_attribute, + "present_value", ), - 107: DPToAttributeMapping( - TuyaMCUCluster.ep_attribute, - "dp_107", + 3: DPToAttributeMapping( + TuyaMmwRadarMinRange.ep_attribute, + "present_value", + endpoint_id=2, ), - 108: DPToAttributeMapping( - TuyaMCUCluster.ep_attribute, - "dp_108", + 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), ), } @@ -216,16 +343,74 @@ class MmwRadarManufCluster(TuyaMCUCluster): 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", + 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", } @@ -258,6 +443,8 @@ def handle_cluster_request( class TuyaMotion(CustomDevice): """BW-IS3 occupancy sensor.""" + quirk_id = TUYA_MMWRADAR + def __init__(self, *args, **kwargs): """Init device.""" self.motion_bus = Bus() @@ -297,6 +484,8 @@ def __init__(self, *args, **kwargs): class NeoMotion(CustomDevice): """NAS-PD07 occupancy sensor.""" + quirk_id = TUYA_MMWRADAR + signature = { # endpoint=1 profile=260 device_type=81 device_version=0 input_clusters=[0, 4, 5, 61184] # output_clusters=[10, 25]> @@ -339,19 +528,18 @@ class NeoMotion(CustomDevice): } -class MmwRadarMotion(CustomDevice): +class TuyaMmwRadarOccupancyVariant1GPP(CustomDevice): """Millimeter wave occupancy sensor.""" + quirk_id = TUYA_MMWRADAR + signature = { # endpoint=1, profile=260, device_type=81, device_version=1, - # input_clusters=[0, 4, 5, 61184], output_clusters=[25, 10] + # input_clusters=[4, 5, 61184, 0], output_clusters=[25, 10]) MODELS_INFO: [ ("_TZE200_ar0slwnd", "TS0601"), ("_TZE200_sfiy5tfs", "TS0601"), ("_TZE200_mrf6vtua", "TS0601"), - ("_TZE200_ztc6ggyl", "TS0601"), - ("_TZE204_ztc6ggyl", "TS0601"), - ("_TZE200_wukb7rhc", "TS0601"), ], ENDPOINTS: { 1: { @@ -365,6 +553,15 @@ class MmwRadarMotion(CustomDevice): ], OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id], }, + 242: { + #