zigpy / zha-device-handlers

ZHA device handlers bridge the functionality gap created when manufacturers deviate from the ZCL specification, handling deviations and exceptions by parsing custom messages to and from Zigbee devices.
Apache License 2.0
731 stars 673 forks source link

TS0601 _TZE200_kb5noeto Tuya Presence and Motion Sensor with illuminance #3125

Open jdm09 opened 5 months ago

jdm09 commented 5 months ago

Problem description

Hi, this is another Zigbee Device which is recognized as a default motion sensor in ZHA, but without any possibilities for configurations such as distance zone config, sensitivity config etc. The Device is a battery powered presence and motion detector including illuminance sensor.

I bought this from here: https://de.aliexpress.com/item/1005006190626320.html

Thought, it should be near by this device custom quirk: https://github.com/zigpy/zha-device-handlers/blob/bfbfb4876d1811440b4a05cac9e3b83311d194a8/zhaquirks/tuya/ts0601_motion.py#L353

but its not working.

Signature of this device: { "node_descriptor": "NodeDescriptor(logical_type=<LogicalType.EndDevice: 2>, complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=<FrequencyBand.Freq2400MHz: 8>, mac_capability_flags=<MACCapabilityFlags.AllocateAddress: 128>, manufacturer_code=4742, maximum_buffer_size=66, maximum_incoming_transfer_size=66, server_mask=10752, maximum_outgoing_transfer_size=66, descriptor_capability_field=<DescriptorCapability.NONE: 0>, allocate_address=True, is_alternate_pan_coordinator=False, is_coordinator=False, is_end_device=True, is_full_function_device=False, is_mains_powered=False, is_receiver_on_when_idle=False, is_router=False, *is_security_capable=False)", "endpoints": { "1": { "profile_id": "0x0104", "device_type": "0x0402", "input_clusters": [ "0x0000", "0x0001", "0x0003", "0x0400", "0x0500" ], "output_clusters": [] } }, "manufacturer": "_TZE200_kb5noeto", "model": "TS0601", "class": "zigpy.device.Device" }

Solution description

I tried to put the device model and manufacturer to the quirk above.

Would be glad, if someone could help with the quirk for this device. Thanks.

Screenshots/Video

Screenshots/Video [Paste/upload your media here]

Device signature

Device signature ``` { "node_descriptor": "NodeDescriptor(logical_type=, complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=, mac_capability_flags=, manufacturer_code=4742, maximum_buffer_size=66, maximum_incoming_transfer_size=66, server_mask=10752, maximum_outgoing_transfer_size=66, descriptor_capability_field=, *allocate_address=True, *is_alternate_pan_coordinator=False, *is_coordinator=False, *is_end_device=True, *is_full_function_device=False, *is_mains_powered=False, *is_receiver_on_when_idle=False, *is_router=False, *is_security_capable=False)", "endpoints": { "1": { "profile_id": "0x0104", "device_type": "0x0402", "input_clusters": [ "0x0000", "0x0001", "0x0003", "0x0400", "0x0500" ], "output_clusters": [] } }, "manufacturer": "_TZE200_kb5noeto", "model": "TS0601", "class": "zigpy.device.Device" } ```

Diagnostic information

Diagnostic information ```json [Paste the diagnostic information here] ```

Logs

Logs ```python [Paste the logs here] ```

Custom quirk

Custom quirk ``` """BlitzWolf IS-3/Tuya motion rechargeable occupancy sensor.""" import math from typing import Optional, 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, 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 ( DPToAttributeMapping, TuyaLocalCluster, TuyaManufCluster, TuyaNewManufCluster, ) from zhaquirks.tuya.mcu import TuyaMCUCluster ZONE_TYPE = 0x0001 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 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 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 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), } ) dp_to_attribute: dict[int, DPToAttributeMapping] = { 1: DPToAttributeMapping( TuyaOccupancySensing.ep_attribute, "occupancy", ), 2: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "dp_2", ), 3: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "dp_3", ), 4: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "dp_4", ), 6: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "dp_6", ), 9: DPToAttributeMapping( TuyaAnalogInput.ep_attribute, "present_value", lambda x: x / 100, ), 101: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "dp_101", ), 102: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "dp_102", ), 103: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "dp_103", ), 104: DPToAttributeMapping( TuyaIlluminanceMeasurement.ep_attribute, "measured_value", lambda x: 10000 * math.log10(x) + 1 if x != 0 else 0, ), 105: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "dp_105", ), 106: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "dp_106", ), 107: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "dp_107", ), 108: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "dp_108", ), } 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", 105: "_dp_2_attr_update", 106: "_dp_2_attr_update", 107: "_dp_2_attr_update", 108: "_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 MmwRadarMotion(CustomDevice): """Millimeter wave occupancy sensor.""" signature = { # endpoint=1, profile=260, device_type=81, device_version=1, # input_clusters=[0, 4, 5, 61184], output_clusters=[25, 10] MODELS_INFO: [ ("_TZE200_ar0slwnd", "TS0601"), ("_TZE200_sfiy5tfs", "TS0601"), ("_TZE200_mrf6vtua", "TS0601"), ("_TZE200_ztc6ggyl", "TS0601"), ("_TZE204_ztc6ggyl", "TS0601"), ("_TZE200_wukb7rhc", "TS0601"), ], ENDPOINTS: { 1: { PROFILE_ID: zha.PROFILE_ID, DEVICE_TYPE: zha.DeviceType.SMART_PLUG, INPUT_CLUSTERS: [ Basic.cluster_id, Groups.cluster_id, Scenes.cluster_id, TuyaNewManufCluster.cluster_id, ], OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id], }, }, } replacement = { ENDPOINTS: { 1: { PROFILE_ID: zha.PROFILE_ID, DEVICE_TYPE: zha.DeviceType.OCCUPANCY_SENSOR, INPUT_CLUSTERS: [ Basic.cluster_id, Groups.cluster_id, Scenes.cluster_id, MmwRadarManufCluster, TuyaOccupancySensing, TuyaAnalogInput, TuyaIlluminanceMeasurement, ], OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id], }, } } class MmwRadarMotionGPP(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"), ("_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], }, 242: { #

Additional information

No response

cobirnm commented 4 months ago

@jdm09 I've managed to get this working using the below quirk. I get illuminance and motion. I don't know if I should get ocupancy sensor or settings for the sensitivity. For that I can't help but would be verry interested if someone could help with this. Please note that I'm not a coder and I just did some research and added the model to this quirk.

"""ZY-M100 Human Presence 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,
    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 (
    #DPToAttributeMapping,
    TuyaLocalCluster,
    TuyaManufCluster,
    TuyaNewManufCluster,
    TuyaDPType,

)

from zhaquirks.tuya.mcu import (
    TuyaAttributesCluster,
    DPToAttributeMapping,
    TuyaMCUCluster,
)

ZONE_TYPE = 0x0001

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 MmwRadarManufCluster(TuyaMCUCluster):
    """ZY-M100 manufacturer cluster."""
    attributes = TuyaMCUCluster.attributes.copy()
    attributes.update(
        {
            0xEF00: ("dp_0", t.CharacterString, True),
            0xEF68: ("dp_104", t.enum8, True),
            0xEF69: ("dp_105", t.enum8, True),
            0xEF6A: ("dp_106", t.enum8, True),
            0xEF6B: ("dp_107", t.uint16_t, True),
            0xEF6C: ("dp_108", t.uint16_t, True),
            0xEF6D: ("dp_109", t.uint16_t, True),
            0xEF6E: ("dp_110", t.uint8_t, True),
            0xEF6F: ("dp_111", t.uint8_t, True),
        }
    )

    dp_to_attribute: Dict[int, DPToAttributeMapping] = {
        104: DPToAttributeMapping(
            TuyaIlluminanceMeasurement.ep_attribute,
            "measured_value",
            lambda x: 10000 * math.log10(x) + 1 if x != 0 else 0,
        ),
        1: DPToAttributeMapping(
            TuyaOccupancySensing.ep_attribute,
            "occupancy",
        ),
        2: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "present_value",
            endpoint_id=6,
        ),
        4: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "present_value",
            endpoint_id=3,
        ),
        3: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "present_value",
            endpoint_id=2,
        ),
        109: DPToAttributeMapping(
            TuyaAnalogInput.ep_attribute,
            "Tpresent_value",
            lambda x: x / 100,
        ),
        101: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "present_value",
            endpoint_id=4,
        ),
        102: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "present_value",
            endpoint_id=5,
        ),
    }

    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",
        104: "_dp_2_attr_update",
        105: "_dp_2_attr_update",
        106: "_dp_2_attr_update",
        107: "_dp_2_attr_update",
        108: "_dp_2_attr_update",
        109: "_dp_2_attr_update",
        110: "_dp_2_attr_update",
        111: "_dp_2_attr_update",
    }

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

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

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

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

class 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, 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, 0000)
        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 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 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 MmwRadarMotionZYM100(CustomDevice):
    """Millimeter wave occupancy sensor."""

    signature = {
        MODELS_INFO: [
            ("_TZE200_kb5noeto", "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: 41440,
                DEVICE_TYPE: 97,
                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,
                    MmwRadarManufCluster,
                    TuyaOccupancySensing,
                    TuyaAnalogInput,
                    TuyaIlluminanceMeasurement,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            #start of new replacements
            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,
                ],
            },
            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: [],
            },
            6: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
                INPUT_CLUSTERS: [
                    TuyaMmwRadarSensitivity,
                ],
                OUTPUT_CLUSTERS: [],
            },

            #### end of new replacements.
            242: {
                PROFILE_ID: 41440,
                DEVICE_TYPE: 97,
                INPUT_CLUSTERS: [],
                OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
            },
        }
    }
vinzent commented 4 months ago

@cobirnm how does your _TZE200_kb5noeto device look like? Is it a battery powered device? your definition shows much more functionality than the zigbee2mqtt defines: https://github.com/13717033460/zigbee-herdsman-converters/blob/6c9cf1b0de836ec2172d569568d3c7fe75268958/src/devices/tuya.ts#L5730-L5762

also the ZY-M100 in the name look more like: https://smarthomescene.com/reviews/tuya-zigbee-human-presence-sensor-zy-m100-review/ instead of https://de.aliexpress.com/item/1005006190626320.html .

cobirnm commented 4 months ago

oes your _TZE200_kb5noeto device look like? Is it a battery powered devic

Hello,

this is my device https://www.aliexpress.com/item/1005006851082995.html

Regarding functionality I'm still testing but something strange is happening. Without the above quirk I can't add the device. If I add the quirk the device gets added but when I look at HA the quirk is not being used. Never the less it is working and I can't explain it.

image

image

constapel commented 4 months ago

TS0601 by _TZE200_kb5noeto Also known as Tuya ZG-204ZM. (PIR+radar 24Ghz) https://smarthomescene.com/blog/best-and-worst-presence-sensors-for-home-assistant/#best-battery-presence-sensor Please look into zha drivers. Zigbee2mqtt is no option. Parameters can not be addjusted. Others say device is constantly sending out its state to the coordinator

https://nl.aliexpress.com/item/1005006803782882.html

vinzent commented 4 months ago

my first try to add a quirk for this ZG-204ZM.

Copied into gist: https://gist.github.com/vinzent/2cd645b848fd3b6a0c3e5762956ec89f#file-zg-204zm-py

grafik

cobirnm commented 4 months ago

my first try to add a quirk for this ZG-204ZM.

Copied into gist: https://gist.github.com/vinzent/2cd645b848fd3b6a0c3e5762956ec89f#file-zg-204zm-py

I used your quirk and it seems better. only the setting parameters are missing.

In this review: https://smarthomescene.com/reviews/zigbee-battery-powered-presence-sensor-zg-204zm-review/ it says that Z2M has settings. It would be great to have it! Never the less this looks quite good!

image

vinzent commented 4 months ago

@cobirnm the attributes arent shown "nicely". You should be able to read/write the attributes by visiting the "Manage Zigbee-Device" link in the 3-dots menu:

grafik

then:

grafik

Honestly I really don't know if everything is working fine. The first time I read the attributes, it reported "None". then i wrote some value. Afterwards if I read it will show the value i've saved. But I don't know if it has any effect on the device.

vinzent commented 4 months ago

Something doesnt seem to be quite right:

2024-05-17 16:08:13.116 DEBUG (MainThread) [homeassistant.components.zha.core.cluster_handlers] [0xE4BF:1:0x0400]: cluster_handler[illuminance] attribute_updated - cluster[Illuminance Measurement] attr[measured_value] value[2570]
2024-05-17 16:08:13.444 DEBUG (MainThread) [homeassistant.components.zha.core.cluster_handlers] [0xE4BF:1:0x0400]: cluster_handler[illuminance] attribute_updated - cluster[Illuminance Measurement] attr[measured_value] value[34099]
2024-05-17 16:12:13.169 DEBUG (MainThread) [homeassistant.components.zha.core.cluster_handlers] [0xE4BF:1:0x0400]: cluster_handler[illuminance] attribute_updated - cluster[Illuminance Measurement] attr[measured_value] value[2560]
2024-05-17 16:12:13.647 DEBUG (MainThread) [homeassistant.components.zha.core.cluster_handlers] [0xE4BF:1:0x0400]: cluster_handler[illuminance] attribute_updated - cluster[Illuminance Measurement] attr[measured_value] value[34082]
2024-05-17 16:16:13.219 DEBUG (MainThread) [homeassistant.components.zha.core.cluster_handlers] [0xE4BF:1:0x0400]: cluster_handler[illuminance] attribute_updated - cluster[Illuminance Measurement] attr[measured_value] value[2532]
2024-05-17 16:16:13.778 DEBUG (MainThread) [homeassistant.components.zha.core.cluster_handlers] [0xE4BF:1:0x0400]: cluster_handler[illuminance] attribute_updated - cluster[Illuminance Measurement] attr[measured_value] value[34035]

The illuminance seems to be reported 2x each time.

vinzent commented 4 months ago

I've updated https://gist.github.com/vinzent/2cd645b848fd3b6a0c3e5762956ec89f

seems the IlluminanceMeasurement is binding to the 0x0000 measured_value too and does some calculations. whereas the tuya datapoint would deliver the already calculated value. Don't have enough insights to understand whats going on here. disabling the illuminance measured_value datapoind did fix this issue.

BambamNZ commented 4 months ago

Loaded the quirk which changed it from motion to presence however, it goes to presence detected and does not change at all.

image

I have removed it and readded it as a new name but that did not change anything.

vinzent commented 4 months ago

@BambamNZ it sometimes takes 20-30 minutes to change to unoccupied/no-motion. with or without the quirk. with the quirk enabled, did you try to change some of the settings (distance, detection sensitivity)?

BambamNZ commented 4 months ago

No matter which setting you tweak it detects presence after you install the batteries and no amount of tweaking of settings get it to clear the presence. I put it in a drawer for 4 hours and still shows presence detected.

Motion_State changes between "Large" & "Static" but never goes to "None" which is suspect is why occupancy value remains "1" and not changes to "0". Force occupancy to "0" which clears it in HA, but it never fires again until you remove the batteries and the cycle starts all over with it stuck with presence detected.

cobirnm commented 4 months ago

That is strange. Mine has been verry stable and reliable. Right now it's not at it's final location but rather in a test position and it detects presence with a verry arcuate degree:

image

These are the settings that I have: image

image image

BambamNZ commented 4 months ago

Based off your shared config @cobirnm - Thank you. I've been able to make some progress, been able to get it clear presence automatically, but the time it takes to clear is still strange. It takes between 5 and 15 minutes to clear from when I leave the room, it works just not very predictably right now.

tipofthesowrd commented 4 months ago

I have the same issue as above. The occupancy state (not motion) is being cleared and detected but the clearing time takes a long time. The time to clear seems to be unrelated to the fading time.

Attributes can be written but seem to have no effect. After removing the battery to reboot the device the attributes return to the default state.

My device reports itself as _TZE200_kb5noeto

vinzent commented 4 months ago

i've ordered a tuya zigbee gateway to test the device with the tuya app and see if it works there as expected.

vinzent commented 4 months ago

large rewrite. i've got the tuya zigbee gatewaay and i was able to access the IOT Dev thing (iot.tuya.com) to discover the datapoints:

https://gist.github.com/vinzent/2cd645b848fd3b6a0c3e5762956ec89f

still need to figure out how to implement it as a quirk v2 with exposed config entries. (https://github.com/zigpy/zha-device-handlers/pull/3019)

lukasdchass commented 3 months ago

Thanks a lot for working on this vinzent. Is there any use for the "HumanPresenceSensorManufCluster" on 0xef00? It has loads of settings but I haven't seen it mentioned, but it only appears for me after installing your quirk.

I run zigbee2mqtt on the side for testing and compatibility and haven't been able to get this device working in ZHA as reliably as MQTT sadly. Did you have any plans to continue with your quirk or do you have it working perfectly? Would love to know what settings you have after your V7 as some of the names changed I think as you introduced new settings? Cheers!

digitaltopo commented 1 month ago

I can't seem to get the quirk to work.

I've deleted and re-added the device several times and tried reconfiguring several times. HA logs report that quirks are loaded. Anything I can try?

Screenshot_20240816_182841_Home Assistant

thefunkygibbon commented 1 month ago

So did anyone get this device working correctly? the quirk at the top that was posted doesn't seem to be picked up by zha for the device (have restarted HA and re-added the device).
trying the other script on the gist gives me a little more but all of the "config" aspects don't have names and are blank, I tried to change one to a value and it bizarrely killed my ZHA (had to remove the quirk and reboot before it would successfully initialize again.

oh and checking the state of the entity with the quirk not enabled, it doesn't seem to ever clear the motion. my entity history only shows "motion" or unavailable (during the troubleshooting times) but never clear

thefunkygibbon commented 1 month ago

I've updated https://gist.github.com/vinzent/2cd645b848fd3b6a0c3e5762956ec89f

seems the IlluminanceMeasurement is binding to the 0x0000 measured_value too and does some calculations. whereas the tuya datapoint would deliver the already calculated value. Don't have enough insights to understand whats going on here. disabling the illuminance measured_value datapoind did fix this issue.

not sure if you've tested your latest one, but my ZHA just doesn't start if i have it in my quirks folder at all.

vinzent commented 1 month ago

not sure if you've tested your latest one, but my ZHA just doesn't start if i have it in my quirks folder at all.

latest does just work with >= HA 2024.08 due to api changes.

if you have that, you might provide error messages.

thefunkygibbon commented 1 month ago

not sure if you've tested your latest one, but my ZHA just doesn't start if i have it in my quirks folder at all.

latest does just work with >= HA 2024.08 due to api changes.

if you have that, you might provide error messages.

yes i'm on the latest HA. I've managed to get it working now (as in that it is not rebooting the zha constantly), but its not actually working correctly. I've re-paired the sensor, it doesn't imply its unavailable, but the lux value and occupancy never change

image

BambamNZ commented 1 month ago

not sure if you've tested your latest one, but my ZHA just doesn't start if i have it in my quirks folder at all.

latest does just work with >= HA 2024.08 due to api changes. if you have that, you might provide error messages.

yes i'm on the latest HA. I've managed to get it working now (as in that it is not rebooting the zha constantly), but its not actually working correctly. I've re-paired the sensor, it doesn't imply its unavailable, but the lux value and occupancy never change

Must admit, I gave up after weeks of trying and struggling, the quirk really helped to get things going, however the sensor was just not reliable. Mine would get stuck on "Occupancy Detected" with no state changes for hours and even days. Had to power cycle (remove batteries) every couple of days.

Found this Zigbee motion Sensor that is doing just fine for the application I was intending to use this one.

thefunkygibbon commented 4 weeks ago

not sure if you've tested your latest one, but my ZHA just doesn't start if i have it in my quirks folder at all.

latest does just work with >= HA 2024.08 due to api changes. if you have that, you might provide error messages.

yes i'm on the latest HA. I've managed to get it working now (as in that it is not rebooting the zha constantly), but its not actually working correctly. I've re-paired the sensor, it doesn't imply its unavailable, but the lux value and occupancy never change

Must admit, I gave up after weeks of trying and struggling, the quirk really helped to get things going, however the sensor was just not reliable. Mine would get stuck on "Occupancy Detected" with no state changes for hours and even days. Had to power cycle (remove batteries) every couple of days.

Found this Zigbee motion Sensor that is doing just fine for the application I was intending to use this one.

ah, yeah thats just PIR, the whole point in using this one was it was Radar with PIR as an "assist" to help it with battery life.

tbh i've had nothing but problems with any radar based (bloody tuya) devices , so far this is the 3rd type i've tried. and yet all 3 seem to apparently work great in z2m. i really am in two minds whether to just bite the bullet and move everything over to z2m, despite it being a gigantuan task.

lukasdchass commented 4 weeks ago

Just wanted to inject a bit of hope here with this sensor! It took me weeks of trying different configurations of detection distance, detection sensitivity, and motion detection sensitivity, but I finally got this working perfectly - no false positives, always on when I'm in the room no matter how still I am, etc. It was just about fiddling with those 3 numbers. Mine are 3, 8, and 5 respectively for a small 2 meter by 2 meters ish bathroom.

One thing I did resort to was cleaning and lightly re-soldering the connections for the PIR and RADAR modules after seeing this mentioned elsewhere. Not sure that helped though as I still had issues until I stumbled upon the right combination of settings. Before I got it right my wife described it as "the bathroom is having disco time" with it going on and off! Battery life has also been amazing which is worth noting considering it's a more complex device than a simple PIR.

Finally, one thing I'd recommend is setting the fading time as low as possible (10 seconds) and then just do your fading time manually via the off automation you create, ie: IF: Presence sensor is none FOR 30 seconds THEN turn of lights. Somehow that fading time was always wrong no matter what you set it to. So I decided to take that out of the equation as far as possible.

It's certainly not the best product experience I've had but it's very satisfying once it works. Keep slogging!

lukasdchass commented 4 weeks ago

ps: thanks a lot to @vinzent for your hard work on the quirk !

petarlaf commented 2 weeks ago

Just wanted to inject a bit of hope here with this sensor! It took me weeks of trying different configurations of detection distance, detection sensitivity, and motion detection sensitivity, but I finally got this working perfectly - no false positives, always on when I'm in the room no matter how still I am, etc. It was just about fiddling with those 3 numbers. Mine are 3, 8, and 5 respectively for a small 2 meter by 2 meters ish bathroom.

One thing I did resort to was cleaning and lightly re-soldering the connections for the PIR and RADAR modules after seeing this mentioned elsewhere. Not sure that helped though as I still had issues until I stumbled upon the right combination of settings. Before I got it right my wife described it as "the bathroom is having disco time" with it going on and off! Battery life has also been amazing which is worth noting considering it's a more complex device than a simple PIR.

Finally, one thing I'd recommend is setting the fading time as low as possible (10 seconds) and then just do your fading time manually via the off automation you create, ie: IF: Presence sensor is none FOR 30 seconds THEN turn of lights. Somehow that fading time was always wrong no matter what you set it to. So I decided to take that out of the equation as far as possible.

It's certainly not the best product experience I've had but it's very satisfying once it works. Keep slogging!

is this on ZHA ? Or MQTT?

thefunkygibbon commented 2 weeks ago

I would assume zha given the GitHub page we are in. would be nice if he shared his working quirk though.

nerumo commented 2 weeks ago

@lukasdchass congrats on your success. You mentioned your magic three numbers: 3,8 and 5.

Do you mean:

What values are you using?

petarlaf commented 2 weeks ago

I would assume zha given the GitHub page we are in. would be nice if he shared his working quirk though.

I'd hope so, but I just can't see how what he is saying would work with ZHA atm - unless he has a separate quirk as you say.