Skip to content

Instantly share code, notes, and snippets.

@vinzent
Last active June 15, 2025 13:21
Show Gist options
  • Save vinzent/2cd645b848fd3b6a0c3e5762956ec89f to your computer and use it in GitHub Desktop.
Save vinzent/2cd645b848fd3b6a0c3e5762956ec89f to your computer and use it in GitHub Desktop.
Tuya PIR+MMWaver Presence sensor ZG-204M ZHA Quirk for HomeAssistant
"""
* TS0601 ZG-204ZM
* _TZE200_kb5noeto
* Works with HA 2024.11 - updated by @txip (Update 2)
* https://de.aliexpress.com/item/1005006174074799.html ("Color": Mmwave PIR)
* https://github.com/13717033460/zigbee-herdsman-converters/blob/6c9cf1b0de836ec2172d569568d3c7fe75268958/src/devices/tuya.ts#L5730-L5762
* https://www.zigbee2mqtt.io/devices/ZG-204ZM.html
* https://smarthomescene.com/reviews/zigbee-battery-powered-presence-sensor-zg-204zm-review/
* https://doc.szalarm.com/zg-205ZL/cntop_zigbee_sensor.js
* https://github.com/Koenkk/zigbee2mqtt/issues/21919
"""
import logging
from typing import Final
from zigpy.quirks.v2 import QuirkBuilder
import zigpy.types as t
from zigpy.zcl.foundation import ZCLAttributeDef
from zigpy.zcl.clusters.measurement import (
IlluminanceMeasurement,
OccupancySensing,
)
from zigpy.zcl.clusters.security import IasZone
from zigpy.quirks.v2.homeassistant import EntityPlatform, EntityType
from zhaquirks.tuya import (
TuyaLocalCluster,
TuyaPowerConfigurationCluster2AAA,
)
from zhaquirks.tuya.mcu import TuyaMCUCluster, DPToAttributeMapping
class HumanMotionState(t.enum8):
"""Human Motion State values"""
none = 0x00
large_move = 0x01
small_move = 0x02
breathe = 0x03
class MotionDetectionMode(t.enum8):
"""Motion detection mode values"""
Only_PIR = 0x00
PIR_radar = 0x01
Only_radar = 0x02
@staticmethod
def converter(value):
"""" If value is None, Only_PIR should be returned """
if value is None:
return MotionDetectionMode.Only_PIR
return value
class TuyaOccupancySensing(OccupancySensing, TuyaLocalCluster):
"""Tuya local OccupancySensing cluster."""
class TuyaIlluminanceMeasurement(IlluminanceMeasurement, TuyaLocalCluster):
"""Tuya local IlluminanceMeasurement cluster."""
class HumanPresenceSensorManufCluster(TuyaMCUCluster):
"""Human Presence Sensor ZG-204ZM (PIR+mmWave, battery)"""
# Tuya Data points
# "1":"Human Presence State", (presence_state, Enum, none|presence)
# "2":"Stationary detection sensitivity", (sensitivity, Integer, 0-10, unit=x, step=1)
# "3":"Minimum detection distance", (near_detection, Integer, 0-1000, unit=cm, step=1) (NOT AVAILABLE IN TUYA SMART LIFE APP)
# "4":"Stationary detection distance", (far_detection, Integer, 0-1000, unit=cm, step=1)
# "101":"Human Motion State", (human_motion_state, Enum, none|large_move|small_move|breathe)
# "102":"Presence Keep Time", (presence_time, 10-28800, unit=s, step=1)
# "106":"Illuminance Value", (illuminance_value, Integer, 0-6000, unit=lux )
# "107":"Indicator", (indicator, Boolean)
# "112":"Reset setting", (reset_setting, Boolean)
# "121":"Battery", (battery, Integer, -1-100, step=1, unit=%)
# "122":"Motion detection ", (motion_detection_mode, Enum, Only_PIR|PIR_radar|Only_radar) (NOT AVAILABLE IN TUYA SMART LIFE APP)
# "123":"Motion detection sensitivity", (motion_detection_sen, Integer, 0-10, step=1, unit=x) (NOT AVAILABLE IN TUYA SMART LIFE APP)
# "124":"ver" (ver, Integer, 0-100, step=1) (NOT AVAILABLE IN TUYA SMART LIFE APP)
class AttributeDefs(TuyaMCUCluster.AttributeDefs):
"""Tuya DataPoints attributes"""
# Human presence state (mapped to the OccupancySensing cluster)
#presence_state: Final = ZCLAttributeDef(
# id=0xEF01, # DP 1
# type=Occupancy,
# access="rp",
# is_manufacturer_specific=True,
#)
# Stationary detection sensitivity
sensitivity: Final = ZCLAttributeDef(
id=0x0002, # DP 2
type=t.uint16_t,
is_manufacturer_specific=True,
)
# Minimum detection distance
near_detection: Final = ZCLAttributeDef(
id=0x0003, # DP 3
type=t.uint16_t,
is_manufacturer_specific=True,
)
# Stationary detection distance
far_detection: Final = ZCLAttributeDef(
id=0x0004, # DP 4
type=t.uint16_t,
is_manufacturer_specific=True,
)
# Human motion state
human_motion_state: Final = ZCLAttributeDef(
id=0x0101, # DP 101
type=HumanMotionState,
access="rp",
is_manufacturer_specific=True,
)
# Presence keep time
presence_time: Final = ZCLAttributeDef(
id=0x0102, # DP 102
type=t.uint16_t,
is_manufacturer_specific=True,
)
# Illuminance value
illuminance_value: Final = ZCLAttributeDef(
id=0x0106, # DP 106
type=t.uint16_t,
access="rp",
is_manufacturer_specific=True,
)
# Indicator
indicator: Final = ZCLAttributeDef(
id=0x0107, # DP 107
type=t.Bool,
is_manufacturer_specific=True,
)
# Reset setting
reset_setting: Final = ZCLAttributeDef(
id=0x0112, # DP 112
type=t.Bool,
is_manufacturer_specific=True,
)
# Battery (also provided by the TuyaPowerConfigurationCluster2AAA)
battery: Final = ZCLAttributeDef(
id=0x0121, # DP 121
type=t.int16s,
is_manufacturer_specific=True,
)
# Motion detection
motion_detection_mode: Final = ZCLAttributeDef(
id=0x0122, # DP 122
type=MotionDetectionMode,
is_manufacturer_specific=True,
)
# Motion detection sensitivity
motion_detection_sen: Final = ZCLAttributeDef(
id=0x0123, # DP 123
type=t.uint16_t,
is_manufacturer_specific=True,
)
# ver
ver: Final = ZCLAttributeDef(
id=0x0124, # DP 124
type=t.uint16_t,
is_manufacturer_specific=True,
)
dp_to_attribute: dict[int, DPToAttributeMapping] = {
1: DPToAttributeMapping(
TuyaOccupancySensing.ep_attribute,
"occupancy",
),
2: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"sensitivity",
# Value in Tuya App after Factory reset is 6
converter=lambda x: x if x is not None else 6
),
3: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"near_detection",
# Guessing a default of 0
converter=lambda x: x if x is not None else 0
),
4: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"far_detection",
# Value in Tuya App after Factory reset is 600cm
converter=lambda x: x if x is not None else 600
),
101: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"human_motion_state",
converter=HumanMotionState
),
102: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"presence_time",
# Value in Tuya App is 30 after Factory reset
converter=lambda x: x if x is not None else 30
),
106: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"illuminance_value",
),
107: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"indicator",
),
112: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"reset_setting",
),
121: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"battery",
),
122: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"motion_detection_mode",
converter=MotionDetectionMode,
),
123: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"motion_detection_sen",
# Guessing a default of 10
converter=lambda x: x if x is not None else 10
),
124: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"ver",
),
}
data_point_handlers = {
1: "_dp_2_attr_update",
2: "_dp_2_attr_update",
3: "_dp_2_attr_update",
4: "_dp_2_attr_update",
101: "_dp_2_attr_update",
102: "_dp_2_attr_update",
106: "_dp_2_attr_update",
107: "_dp_2_attr_update",
112: "_dp_2_attr_update",
121: "_dp_2_attr_update",
122: "_dp_2_attr_update",
123: "_dp_2_attr_update",
124: "_dp_2_attr_update",
}
(
QuirkBuilder("_TZE200_kb5noeto", "TS0601")
.skip_configuration()
.removes(IasZone.cluster_id)
.adds(HumanPresenceSensorManufCluster)
.adds(TuyaOccupancySensing)
.replaces(TuyaPowerConfigurationCluster2AAA)
.replaces(TuyaIlluminanceMeasurement)
.number(
HumanPresenceSensorManufCluster.AttributeDefs.sensitivity.name,
HumanPresenceSensorManufCluster.cluster_id,
step=1,
min_value=1,
max_value=10,
fallback_name="Sensitivity",
translation_key="sensitivity"
)
.number(
HumanPresenceSensorManufCluster.AttributeDefs.near_detection.name,
HumanPresenceSensorManufCluster.cluster_id,
step=1,
min_value=0,
max_value=1000,
fallback_name="Near Detection",
translation_key="near_detection"
)
.number(
HumanPresenceSensorManufCluster.AttributeDefs.far_detection.name,
HumanPresenceSensorManufCluster.cluster_id,
step=1,
min_value=0,
max_value=1000,
fallback_name="Far Detection",
translation_key="far_detection"
)
.enum(
HumanPresenceSensorManufCluster.AttributeDefs.human_motion_state.name,
HumanMotionState,
HumanPresenceSensorManufCluster.cluster_id,
entity_platform=EntityPlatform.SENSOR,
entity_type=EntityType.STANDARD,
fallback_name="Human Motion State",
translation_key="human_motion_state"
)
.number(
HumanPresenceSensorManufCluster.AttributeDefs.presence_time.name,
HumanPresenceSensorManufCluster.cluster_id,
step=1,
min_value=10,
max_value=28800,
fallback_name="Presence Time",
translation_key="presence_time"
)
.switch(
HumanPresenceSensorManufCluster.AttributeDefs.indicator.name,
HumanPresenceSensorManufCluster.cluster_id,
fallback_name="Indicator",
translation_key="indicator"
)
# .binary_sensor(
# HumanPresenceSensorManufCluster.AttributeDefs.reset_setting.name,
# HumanPresenceSensorManufCluster.cluster_id
# )
.enum(
HumanPresenceSensorManufCluster.AttributeDefs.motion_detection_mode.name,
MotionDetectionMode,
HumanPresenceSensorManufCluster.cluster_id,
fallback_name="Motion Detection Mode",
translation_key="motion_detection_mode"
)
.number(
HumanPresenceSensorManufCluster.AttributeDefs.motion_detection_sen.name,
HumanPresenceSensorManufCluster.cluster_id,
step=1,
min_value=0,
max_value=10,
fallback_name="Motion Detection Sensitivity",
translation_key="motion_detection_sen"
)
# .number(
# HumanPresenceSensorManufCluster.AttributeDefs.ver.name,
# HumanPresenceSensorManufCluster.cluster_id,
# step=1,
# min_value=0,
# max_value=10
# )
.add_to_registry()
)
@jamesshannon
Copy link

It's as if the mmWave sensor never actually works

That's exactly what I've been experiencing. It behaves as if it's just a PIR sensor. Also, I've never gotten a BREATHING value out of it (like some others have reported). I even opened it up to confirm that I was sent the correct part device. After seeing the thread where others found that the PIR sensor wasn't soldered correctly, I wonder if there might be a batch where the mmwave sensor doesn't work and the device silently falls back to PIR-only.

or gets used by the quirk?

As far as I know, the quirk isn't what's "using" the outputs of the two sensors -- the device does that then presents the occupancy state.
With that being said, the quirk does expose the https://gist.github.com/vinzent/2cd645b848fd3b6a0c3e5762956ec89f#file-zg-204zm-py-L39, and for some reason defaults to PIR_only, though i think that's only for display purposes? I tried to set the mode to 2 (which, when the value was read back displayed as Mode.undefined_0x02, so I'm not sure I did it correctly), but it didn't make any difference.

@txip
Copy link

txip commented Oct 17, 2024

Try to set it to mode 1 (PIR + radar) (just write "1" with no brackets).
Some devices go with mode not set at all initially.
I'm not sure the mode 2 (radar only) is operable for the battery equipped version cos its' main idea is to turn on mmwave radar only when PIR sensor gets active.

@greenamit
Copy link

greenamit commented Nov 7, 2024

I was using the following quirk for months and it worked quite well.
After updating HA to 2024.11.0 the device is shown as motion only. I tried to remove/add it but it doesn't seem to get the quirk profile.
Screenshot 2024-11-07 at 16 25 58

"""
 * TS0601 ZG-204ZM
 * _TZE200_kb5noeto
 * https://de.aliexpress.com/item/1005006174074799.html ("Color": Mmwave PIR)
 * https://github.com/13717033460/zigbee-herdsman-converters/blob/6c9cf1b0de836ec2172d569568d3c7fe75268958/src/devices/tuya.ts#L5730-L5762
 * https://www.zigbee2mqtt.io/devices/ZG-204ZM.html
 * https://smarthomescene.com/reviews/zigbee-battery-powered-presence-sensor-zg-204zm-review/
 * https://doc.szalarm.com/zg-205ZL/cntop_zigbee_sensor.js
 * https://github.com/Koenkk/zigbee2mqtt/issues/21919
"""

import logging
from typing import Final

from zigpy.quirks.v2 import QuirkBuilder

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

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

class HumanMotionState(t.enum8):
    """Human Motion State values"""
    none = 0x00
    large_move = 0x01
    small_move = 0x02
    breathe = 0x03

class MotionDetectionMode(t.enum8):
    """Motion detection mode values"""
    Only_PIR: 0x00
    PIR_radar: 0x01
    Only_radar: 0x02

    @staticmethod
    def converter(value):
        """" If value  is None, Only_PIR should be returned """
                
        if value is None:
            return MotionDetectionMode.Only_PIR
        return value


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


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

class HumanPresenceSensorManufCluster(TuyaMCUCluster):
    """Human Presence Sensor ZG-204ZM (PIR+mmWave, battery)"""

    # Tuya Data points
    # "1":"Human Presence State", (presence_state, Enum, none|presence)
    # "2":"Stationary detection sensitivity", (sensitivity, Integer, 0-10, unit=x, step=1)
    # "3":"Minimum detection distance", (near_detection, Integer, 0-1000, unit=cm, step=1) (NOT AVAILABLE IN TUYA SMART LIFE APP)
    # "4":"Stationary detection distance", (far_detection, Integer, 0-1000, unit=cm, step=1)
    # "101":"Human Motion State", (human_motion_state, Enum, none|large_move|small_move|breathe)
    # "102":"Presence Keep Time", (presence_time, 10-28800, unit=s, step=1)
    # "106":"Illuminance Value", (illuminance_value, Integer, 0-6000, unit=lux )
    # "107":"Indicator", (indicator, Boolean)
    # "112":"Reset setting", (reset_setting, Boolean)
    # "121":"Battery", (battery, Integer, -1-100, step=1, unit=%)
    # "122":"Motion detection ", (motion_detection_mode, Enum, Only_PIR|PIR_radar|Only_radar) (NOT AVAILABLE IN TUYA SMART LIFE APP)
    # "123":"Motion detection sensitivity", (motion_detection_sen, Integer, 0-10, step=1, unit=x) (NOT AVAILABLE IN TUYA SMART LIFE APP)
    # "124":"ver" (ver, Integer, 0-100, step=1) (NOT AVAILABLE IN TUYA SMART LIFE APP)


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

        # Human presence state (mapped to the OccupancySensing cluster)
        #presence_state: Final = ZCLAttributeDef(
        #    id=0xEF01, # DP 1
        #    type=Occupancy,
        #    access="rp",
        #    is_manufacturer_specific=True,
        #)

        # Stationary detection sensitivity
        sensitivity: Final = ZCLAttributeDef(
            id=0x0002, # DP 2
            type=t.uint16_t,
            is_manufacturer_specific=True,
        )
        # Minimum detection distance
        near_detection: Final = ZCLAttributeDef(
            id=0x0003, # DP 3
            type=t.uint16_t,
            is_manufacturer_specific=True,
        )
        # Stationary detection distance
        far_detection: Final = ZCLAttributeDef(
            id=0x0004, # DP 4
            type=t.uint16_t,
            is_manufacturer_specific=True,
        )
        # Human motion state
        human_motion_state: Final = ZCLAttributeDef(
            id=0x0101, # DP 101
            type=HumanMotionState,
            access="rp",
            is_manufacturer_specific=True,
        )
        # Presence keep time
        presence_time: Final = ZCLAttributeDef(
            id=0x0102, # DP 102
            type=t.uint16_t,
            is_manufacturer_specific=True,
        )
        # Illuminance value
        illuminance_value: Final = ZCLAttributeDef(
            id=0x0106, # DP 106
            type=t.uint16_t,
            access="rp",
            is_manufacturer_specific=True,
        )
        # Indicator
        indicator: Final = ZCLAttributeDef(
            id=0x0107, # DP 107
            type=t.Bool,
            is_manufacturer_specific=True,
        )
        # Reset setting
        reset_setting: Final = ZCLAttributeDef(
            id=0x0112, # DP 112
            type=t.Bool,
            is_manufacturer_specific=True,
        )
        # Battery (also provided by the TuyaPowerConfigurationCluster2AAA)
        battery: Final = ZCLAttributeDef(
            id=0x0121, # DP 121
            type=t.int16s,
            is_manufacturer_specific=True,
        )
        # Motion detection
        motion_detection_mode: Final = ZCLAttributeDef(
            id=0x0122, # DP 122
            type=MotionDetectionMode,
            is_manufacturer_specific=True,
        )
        # Motion detection sensitivity
        motion_detection_sen: Final = ZCLAttributeDef(
            id=0x0123, # DP 123
            type=t.uint16_t,
            is_manufacturer_specific=True,
        )
        # ver
        ver: Final = ZCLAttributeDef(
            id=0x0124, # DP 124
            type=t.uint16_t,
            is_manufacturer_specific=True,
        )

    dp_to_attribute: dict[int, DPToAttributeMapping] = {
        1: DPToAttributeMapping(
            TuyaOccupancySensing.ep_attribute,
            "occupancy",
        ),
        2: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "sensitivity",
            # Value in Tuya App after Factory reset is 6
            converter=lambda x: x if x is not None else 6
        ),
        3: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "near_detection",
            # Guessing a default of 0
            converter=lambda x: x if x is not None else 0
        ),
        4: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "far_detection",
            # Value in Tuya App after Factory reset is 600cm
            converter=lambda x: x if x is not None else 600
        ),
        101: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "human_motion_state",
            converter=HumanMotionState
        ),
        102: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "presence_time",
            # Value in Tuya App is 30 after Factory reset
            converter=lambda x: x if x is not None else 30
        ),
        106: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "illuminance_value",
        ),
        107: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "indicator",
        ),
        112: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "reset_setting",
        ),
        121: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "battery",
        ),
        122: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "motion_detection_mode",
            converter=MotionDetectionMode,
        ),
        123: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "motion_detection_sen",
            # Guessing a default of 10
            converter=lambda x: x if x is not None else 10
        ),
        124: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "ver",
        ),
    }

    data_point_handlers = {
        1: "_dp_2_attr_update",
        2: "_dp_2_attr_update",
        3: "_dp_2_attr_update",
        4: "_dp_2_attr_update",
        101: "_dp_2_attr_update",
        102: "_dp_2_attr_update",
        106: "_dp_2_attr_update",
        107: "_dp_2_attr_update",
        112: "_dp_2_attr_update",
        121: "_dp_2_attr_update",
        122: "_dp_2_attr_update",
        123: "_dp_2_attr_update",
        124: "_dp_2_attr_update",
    }

(
    QuirkBuilder("_TZE200_kb5noeto", "TS0601")
    .skip_configuration()
    .removes(IasZone.cluster_id)
    .adds(HumanPresenceSensorManufCluster)
    .adds(TuyaOccupancySensing)
    .replaces(TuyaPowerConfigurationCluster2AAA)
    .replaces(TuyaIlluminanceMeasurement)
    .number(HumanPresenceSensorManufCluster.AttributeDefs.sensitivity.name, HumanPresenceSensorManufCluster.cluster_id,step=1,min_value=1,max_value=10)
    #.number(HumanPresenceSensorManufCluster.AttributeDefs.near_detection.name, HumanPresenceSensorManufCluster.cluster_id,step=1,min_value=0,max_value=1000)
    .number(HumanPresenceSensorManufCluster.AttributeDefs.far_detection.name, HumanPresenceSensorManufCluster.cluster_id,step=1,min_value=0,max_value=1000)
    .enum(HumanPresenceSensorManufCluster.AttributeDefs.human_motion_state.name,HumanMotionState,HumanPresenceSensorManufCluster.cluster_id,entity_platform=EntityPlatform.SENSOR, entity_type=EntityType.STANDARD)
    .number(HumanPresenceSensorManufCluster.AttributeDefs.presence_time.name, HumanPresenceSensorManufCluster.cluster_id,step=1,min_value=10,max_value=28800)
    #.binary_sensor(HumanPresenceSensorManufCluster.AttributeDefs.indicator.name,HumanPresenceSensorManufCluster.cluster_id)
    #.binary_sensor(HumanPresenceSensorManufCluster.AttributeDefs.reset_setting.name,HumanPresenceSensorManufCluster.cluster_id)
    #.enum(HumanPresenceSensorManufCluster.AttributeDefs.motion_detection_mode.name,MotionDetectionMode,HumanPresenceSensorManufCluster.cluster_id)
    #.number(HumanPresenceSensorManufCluster.AttributeDefs.motion_detection_sen.name, HumanPresenceSensorManufCluster.cluster_id,step=1,min_value=0,max_value=10)
    #.number(HumanPresenceSensorManufCluster.AttributeDefs.ver.name, HumanPresenceSensorManufCluster.cluster_id,step=1,min_value=0,max_value=10)
    .add_to_registry()
)

@txip
Copy link

txip commented Nov 7, 2024

Try this one

"""
 * TS0601 ZG-204ZM
 * _TZE200_kb5noeto
 * Works with HA 2024.11
 * https://de.aliexpress.com/item/1005006174074799.html ("Color": Mmwave PIR)
 * https://github.com/13717033460/zigbee-herdsman-converters/blob/6c9cf1b0de836ec2172d569568d3c7fe75268958/src/devices/tuya.ts#L5730-L5762
 * https://www.zigbee2mqtt.io/devices/ZG-204ZM.html
 * https://smarthomescene.com/reviews/zigbee-battery-powered-presence-sensor-zg-204zm-review/
 * https://doc.szalarm.com/zg-205ZL/cntop_zigbee_sensor.js
 * https://github.com/Koenkk/zigbee2mqtt/issues/21919
"""

import logging
from typing import Final

from zigpy.quirks.v2 import QuirkBuilder

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

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

class HumanMotionState(t.enum8):
    """Human Motion State values"""
    none = 0x00
    large_move = 0x01
    small_move = 0x02
    breathe = 0x03

class MotionDetectionMode(t.enum8):
    """Motion detection mode values"""
    Only_PIR = 0x00
    PIR_radar = 0x01
    Only_radar = 0x02

    @staticmethod
    def converter(value):
        """" If value  is None, Only_PIR should be returned """
                
        if value is None:
            return MotionDetectionMode.Only_PIR
        return value


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


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

class HumanPresenceSensorManufCluster(TuyaMCUCluster):
    """Human Presence Sensor ZG-204ZM (PIR+mmWave, battery)"""

    # Tuya Data points
    # "1":"Human Presence State", (presence_state, Enum, none|presence)
    # "2":"Stationary detection sensitivity", (sensitivity, Integer, 0-10, unit=x, step=1)
    # "3":"Minimum detection distance", (near_detection, Integer, 0-1000, unit=cm, step=1) (NOT AVAILABLE IN TUYA SMART LIFE APP)
    # "4":"Stationary detection distance", (far_detection, Integer, 0-1000, unit=cm, step=1)
    # "101":"Human Motion State", (human_motion_state, Enum, none|large_move|small_move|breathe)
    # "102":"Presence Keep Time", (presence_time, 10-28800, unit=s, step=1)
    # "106":"Illuminance Value", (illuminance_value, Integer, 0-6000, unit=lux )
    # "107":"Indicator", (indicator, Boolean)
    # "112":"Reset setting", (reset_setting, Boolean)
    # "121":"Battery", (battery, Integer, -1-100, step=1, unit=%)
    # "122":"Motion detection ", (motion_detection_mode, Enum, Only_PIR|PIR_radar|Only_radar) (NOT AVAILABLE IN TUYA SMART LIFE APP)
    # "123":"Motion detection sensitivity", (motion_detection_sen, Integer, 0-10, step=1, unit=x) (NOT AVAILABLE IN TUYA SMART LIFE APP)
    # "124":"ver" (ver, Integer, 0-100, step=1) (NOT AVAILABLE IN TUYA SMART LIFE APP)


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

        # Human presence state (mapped to the OccupancySensing cluster)
        #presence_state: Final = ZCLAttributeDef(
        #    id=0xEF01, # DP 1
        #    type=Occupancy,
        #    access="rp",
        #    is_manufacturer_specific=True,
        #)

        # Stationary detection sensitivity
        sensitivity: Final = ZCLAttributeDef(
            id=0x0002, # DP 2
            type=t.uint16_t,
            is_manufacturer_specific=True,
        )
        # Minimum detection distance
        near_detection: Final = ZCLAttributeDef(
            id=0x0003, # DP 3
            type=t.uint16_t,
            is_manufacturer_specific=True,
        )
        # Stationary detection distance
        far_detection: Final = ZCLAttributeDef(
            id=0x0004, # DP 4
            type=t.uint16_t,
            is_manufacturer_specific=True,
        )
        # Human motion state
        human_motion_state: Final = ZCLAttributeDef(
            id=0x0101, # DP 101
            type=HumanMotionState,
            access="rp",
            is_manufacturer_specific=True,
        )
        # Presence keep time
        presence_time: Final = ZCLAttributeDef(
            id=0x0102, # DP 102
            type=t.uint16_t,
            is_manufacturer_specific=True,
        )
        # Illuminance value
        illuminance_value: Final = ZCLAttributeDef(
            id=0x0106, # DP 106
            type=t.uint16_t,
            access="rp",
            is_manufacturer_specific=True,
        )
        # Indicator
        indicator: Final = ZCLAttributeDef(
            id=0x0107, # DP 107
            type=t.Bool,
            is_manufacturer_specific=True,
        )
        # Reset setting
        reset_setting: Final = ZCLAttributeDef(
            id=0x0112, # DP 112
            type=t.Bool,
            is_manufacturer_specific=True,
        )
        # Battery (also provided by the TuyaPowerConfigurationCluster2AAA)
        battery: Final = ZCLAttributeDef(
            id=0x0121, # DP 121
            type=t.int16s,
            is_manufacturer_specific=True,
        )
        # Motion detection
        motion_detection_mode: Final = ZCLAttributeDef(
            id=0x0122, # DP 122
            type=MotionDetectionMode,
            is_manufacturer_specific=True,
        )
        # Motion detection sensitivity
        motion_detection_sen: Final = ZCLAttributeDef(
            id=0x0123, # DP 123
            type=t.uint16_t,
            is_manufacturer_specific=True,
        )
        # ver
        ver: Final = ZCLAttributeDef(
            id=0x0124, # DP 124
            type=t.uint16_t,
            is_manufacturer_specific=True,
        )

    dp_to_attribute: dict[int, DPToAttributeMapping] = {
        1: DPToAttributeMapping(
            TuyaOccupancySensing.ep_attribute,
            "occupancy",
        ),
        2: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "sensitivity",
            # Value in Tuya App after Factory reset is 6
            converter=lambda x: x if x is not None else 6
        ),
        3: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "near_detection",
            # Guessing a default of 0
            converter=lambda x: x if x is not None else 0
        ),
        4: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "far_detection",
            # Value in Tuya App after Factory reset is 600cm
            converter=lambda x: x if x is not None else 600
        ),
        101: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "human_motion_state",
            converter=HumanMotionState
        ),
        102: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "presence_time",
            # Value in Tuya App is 30 after Factory reset
            converter=lambda x: x if x is not None else 30
        ),
        106: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "illuminance_value",
        ),
        107: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "indicator",
        ),
        112: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "reset_setting",
        ),
        121: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "battery",
        ),
        122: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "motion_detection_mode",
            converter=MotionDetectionMode,
        ),
        123: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "motion_detection_sen",
            # Guessing a default of 10
            converter=lambda x: x if x is not None else 10
        ),
        124: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "ver",
        ),
    }

    data_point_handlers = {
        1: "_dp_2_attr_update",
        2: "_dp_2_attr_update",
        3: "_dp_2_attr_update",
        4: "_dp_2_attr_update",
        101: "_dp_2_attr_update",
        102: "_dp_2_attr_update",
        106: "_dp_2_attr_update",
        107: "_dp_2_attr_update",
        112: "_dp_2_attr_update",
        121: "_dp_2_attr_update",
        122: "_dp_2_attr_update",
        123: "_dp_2_attr_update",
        124: "_dp_2_attr_update",
    }

(
    QuirkBuilder("_TZE200_kb5noeto", "TS0601")
    .skip_configuration()
    .removes(IasZone.cluster_id)
    .adds(HumanPresenceSensorManufCluster)
    .adds(TuyaOccupancySensing)
    .replaces(TuyaPowerConfigurationCluster2AAA)
    .replaces(TuyaIlluminanceMeasurement)
    .number(
        HumanPresenceSensorManufCluster.AttributeDefs.sensitivity.name,
        HumanPresenceSensorManufCluster.cluster_id,
        step=1,
        min_value=1,
        max_value=10,
        fallback_name="Sensitivity",
        translation_key="sensitivity"
    )
    .number(
        HumanPresenceSensorManufCluster.AttributeDefs.near_detection.name,
        HumanPresenceSensorManufCluster.cluster_id,
        step=1,
        min_value=0,
        max_value=1000,
        fallback_name="Near Detection",
        translation_key="near_detection"
    )
    .number(
        HumanPresenceSensorManufCluster.AttributeDefs.far_detection.name,
        HumanPresenceSensorManufCluster.cluster_id,
        step=1,
        min_value=0,
        max_value=1000,
        fallback_name="Far Detection",
        translation_key="far_detection"
    )
    .enum(
        HumanPresenceSensorManufCluster.AttributeDefs.human_motion_state.name,
        HumanMotionState,
        HumanPresenceSensorManufCluster.cluster_id,
        entity_platform=EntityPlatform.SENSOR,
        entity_type=EntityType.STANDARD,
        fallback_name="Human Motion State",
        translation_key="human_motion_state"
    )
    .number(
        HumanPresenceSensorManufCluster.AttributeDefs.presence_time.name,
        HumanPresenceSensorManufCluster.cluster_id,
        step=1,
        min_value=10,
        max_value=28800,
        fallback_name="Presence Time",
        translation_key="presence_time"
    )
    .switch(
        HumanPresenceSensorManufCluster.AttributeDefs.indicator.name,
        HumanPresenceSensorManufCluster.cluster_id,
        fallback_name="Indicator",
        translation_key="indicator"
    )
#    .binary_sensor(
#        HumanPresenceSensorManufCluster.AttributeDefs.reset_setting.name,
#        HumanPresenceSensorManufCluster.cluster_id
#    )
    .enum(
        HumanPresenceSensorManufCluster.AttributeDefs.motion_detection_mode.name,
        MotionDetectionMode,
        HumanPresenceSensorManufCluster.cluster_id,
        fallback_name="Motion Detection Mode",
        translation_key="motion_detection_mode"
    )
    .number(
        HumanPresenceSensorManufCluster.AttributeDefs.motion_detection_sen.name,
        HumanPresenceSensorManufCluster.cluster_id,
        step=1,
        min_value=0,
        max_value=10,
        fallback_name="Motion Detection Sensitivity",
        translation_key="motion_detection_sen"
    )
#    .number(
#        HumanPresenceSensorManufCluster.AttributeDefs.ver.name,
#        HumanPresenceSensorManufCluster.cluster_id,
#        step=1,
#        min_value=0,
#        max_value=10
#    )
    .add_to_registry()
)

@bigkevuk
Copy link

@txip Thank you so much, did the update over the weekend then noticed it all stopped and your fix has given me occupancy back :-) Would seem that in the upgrades the old code returned a NONE instead of a number which caused the quirk not to load.

Yours seems to be much cleaner implentation / modern version .

Thanks again!

@tnaseem
Copy link

tnaseem commented Nov 11, 2024

Same here. Your updated code fixed it for me too. Many thanks, @txip!

@vinzent
Copy link
Author

vinzent commented Nov 11, 2024

seems there are more changes to come with qurikbuilder and tuya: zigpy/zha-device-handlers#3417

@txip
Copy link

txip commented Nov 13, 2024

I've updated the quirk a bit more here
I exposed some more settings and indicators so now the following settings|features are available:

  • Near Detection
  • Sensitivity
  • Presence Time
  • Far Detection
  • Motion Detection Sensitivity
  • Motion Detection Mode (you can switch between PIR only, PIR+radar and Radar only modes). Honestly I'm not sure that Radar only mode even works on this device.
  • Indicator switch (you can turn on|off red light that flashes when detects any movement)

Vinzent did a great job, I've made just minor changes to the quirk tbf so all thankyous go to him. ;-)

@maciejkopecpl
Copy link

Nice! Can you guys tell me what these settings controls?
Near Detection v Far Detection
Sensitivity v Motion Detection Sensitivity
Presence Time

@txip
Copy link

txip commented Nov 13, 2024

You can get some understanding from the User Manual here

@petarlaf
Copy link

txip thanks a lot for your work on this - this makes this a lot more usable!

One question on that documentation - they talk heavily about different sensitivity settings for "large motion" and "small motion" sensitivity - which one is the current sensitivity setting?
image

@darth-hp
Copy link

Thx @txip!

@robert4y
Copy link

robert4y commented Nov 28, 2024

"""

import logging
from typing import Final

from zigpy.quirks.v2 import QuirkBuilder

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

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

class HumanMotionState(t.enum8):
"""Human Motion State values"""
none = 0x00
large_move = 0x01
small_move = 0x02
breathe = 0x03

class MotionDetectionMode(t.enum8):
"""Motion detection mode values"""
Only_PIR = 0x00
PIR_radar = 0x01
Only_radar = 0x02

@staticmethod
def converter(value):
    """" If value  is None, Only_PIR should be returned """
            
    if value is None:
        return MotionDetectionMode.Only_PIR
    return value

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

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

class HumanPresenceSensorManufCluster(TuyaMCUCluster):
"""Human Presence Sensor ZG-204ZM (PIR+mmWave, battery)"""

# Tuya Data points
# "1":"Human Presence State", (presence_state, Enum, none|presence)
# "2":"Stationary detection sensitivity", (sensitivity, Integer, 0-10, unit=x, step=1)
# "3":"Minimum detection distance", (near_detection, Integer, 0-1000, unit=cm, step=1) (NOT AVAILABLE IN TUYA SMART LIFE APP)
# "4":"Stationary detection distance", (far_detection, Integer, 0-1000, unit=cm, step=1)
# "101":"Human Motion State", (human_motion_state, Enum, none|large_move|small_move|breathe)
# "102":"Presence Keep Time", (presence_time, 10-28800, unit=s, step=1)
# "106":"Illuminance Value", (illuminance_value, Integer, 0-6000, unit=lux )
# "107":"Indicator", (indicator, Boolean)
# "112":"Reset setting", (reset_setting, Boolean)
# "121":"Battery", (battery, Integer, -1-100, step=1, unit=%)
# "122":"Motion detection ", (motion_detection_mode, Enum, Only_PIR|PIR_radar|Only_radar) (NOT AVAILABLE IN TUYA SMART LIFE APP)
# "123":"Motion detection sensitivity", (motion_detection_sen, Integer, 0-10, step=1, unit=x) (NOT AVAILABLE IN TUYA SMART LIFE APP)
# "124":"ver" (ver, Integer, 0-100, step=1) (NOT AVAILABLE IN TUYA SMART LIFE APP)


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

    # Human presence state (mapped to the OccupancySensing cluster)
    #presence_state: Final = ZCLAttributeDef(
    #    id=0xEF01, # DP 1
    #    type=Occupancy,
    #    access="rp",
    #    is_manufacturer_specific=True,
    #)

    # Stationary detection sensitivity
    sensitivity: Final = ZCLAttributeDef(
        id=0x0002, # DP 2
        type=t.uint16_t,
        is_manufacturer_specific=True,
    )
    # Minimum detection distance
    near_detection: Final = ZCLAttributeDef(
        id=0x0003, # DP 3
        type=t.uint16_t,
        is_manufacturer_specific=True,
    )
    # Stationary detection distance
    far_detection: Final = ZCLAttributeDef(
        id=0x0004, # DP 4
        type=t.uint16_t,
        is_manufacturer_specific=True,
    )
    # Human motion state
    human_motion_state: Final = ZCLAttributeDef(
        id=0x0101, # DP 101
        type=HumanMotionState,
        access="rp",
        is_manufacturer_specific=True,
    )
    # Presence keep time
    presence_time: Final = ZCLAttributeDef(
        id=0x0102, # DP 102
        type=t.uint16_t,
        is_manufacturer_specific=True,
    )
    # Illuminance value
    illuminance_value: Final = ZCLAttributeDef(
        id=0x0106, # DP 106
        type=t.uint16_t,
        access="rp",
        is_manufacturer_specific=True,
    )
    # Indicator
    indicator: Final = ZCLAttributeDef(
        id=0x0107, # DP 107
        type=t.Bool,
        is_manufacturer_specific=True,
    )
    # Reset setting
    reset_setting: Final = ZCLAttributeDef(
        id=0x0112, # DP 112
        type=t.Bool,
        is_manufacturer_specific=True,
    )
    # Battery (also provided by the TuyaPowerConfigurationCluster2AAA)
    battery: Final = ZCLAttributeDef(
        id=0x0121, # DP 121
        type=t.int16s,
        is_manufacturer_specific=True,
    )
    # Motion detection
    motion_detection_mode: Final = ZCLAttributeDef(
        id=0x0122, # DP 122
        type=MotionDetectionMode,
        is_manufacturer_specific=True,
    )
    # Motion detection sensitivity
    motion_detection_sen: Final = ZCLAttributeDef(
        id=0x0123, # DP 123
        type=t.uint16_t,
        is_manufacturer_specific=True,
    )
    # ver
    ver: Final = ZCLAttributeDef(
        id=0x0124, # DP 124
        type=t.uint16_t,
        is_manufacturer_specific=True,
    )

dp_to_attribute: dict[int, DPToAttributeMapping] = {
    1: DPToAttributeMapping(
        TuyaOccupancySensing.ep_attribute,
        "occupancy",
    ),
    2: DPToAttributeMapping(
        TuyaMCUCluster.ep_attribute,
        "sensitivity",
        # Value in Tuya App after Factory reset is 6
        converter=lambda x: x if x is not None else 6
    ),
    3: DPToAttributeMapping(
        TuyaMCUCluster.ep_attribute,
        "near_detection",
        # Guessing a default of 0
        converter=lambda x: x if x is not None else 0
    ),
    4: DPToAttributeMapping(
        TuyaMCUCluster.ep_attribute,
        "far_detection",
        # Value in Tuya App after Factory reset is 600cm
        converter=lambda x: x if x is not None else 600
    ),
    101: DPToAttributeMapping(
        TuyaMCUCluster.ep_attribute,
        "human_motion_state",
        converter=HumanMotionState
    ),
    102: DPToAttributeMapping(
        TuyaMCUCluster.ep_attribute,
        "presence_time",
        # Value in Tuya App is 30 after Factory reset
        converter=lambda x: x if x is not None else 30
    ),
    106: DPToAttributeMapping(
        TuyaMCUCluster.ep_attribute,
        "illuminance_value",
    ),
    107: DPToAttributeMapping(
        TuyaMCUCluster.ep_attribute,
        "indicator",
    ),
    112: DPToAttributeMapping(
        TuyaMCUCluster.ep_attribute,
        "reset_setting",
    ),
    121: DPToAttributeMapping(
        TuyaMCUCluster.ep_attribute,
        "battery",
    ),
    122: DPToAttributeMapping(
        TuyaMCUCluster.ep_attribute,
        "motion_detection_mode",
        converter=MotionDetectionMode,
    ),
    123: DPToAttributeMapping(
        TuyaMCUCluster.ep_attribute,
        "motion_detection_sen",
        # Guessing a default of 10
        converter=lambda x: x if x is not None else 10
    ),
    124: DPToAttributeMapping(
        TuyaMCUCluster.ep_attribute,
        "ver",
    ),
}

data_point_handlers = {
    1: "_dp_2_attr_update",
    2: "_dp_2_attr_update",
    3: "_dp_2_attr_update",
    4: "_dp_2_attr_update",
    101: "_dp_2_attr_update",
    102: "_dp_2_attr_update",
    106: "_dp_2_attr_update",
    107: "_dp_2_attr_update",
    112: "_dp_2_attr_update",
    121: "_dp_2_attr_update",
    122: "_dp_2_attr_update",
    123: "_dp_2_attr_update",
    124: "_dp_2_attr_update",
}

(
QuirkBuilder("_TZE200_kb5noeto", "TS0601")
.skip_configuration()
.removes(IasZone.cluster_id)
.adds(HumanPresenceSensorManufCluster)
.adds(TuyaOccupancySensing)
.replaces(TuyaPowerConfigurationCluster2AAA)
.replaces(TuyaIlluminanceMeasurement)
.number(
HumanPresenceSensorManufCluster.AttributeDefs.sensitivity.name,
HumanPresenceSensorManufCluster.cluster_id,
step=1,
min_value=1,
max_value=10,
fallback_name="Sensitivity",
translation_key="sensitivity"
)
.number(
HumanPresenceSensorManufCluster.AttributeDefs.near_detection.name,
HumanPresenceSensorManufCluster.cluster_id,
step=1,
min_value=0,
max_value=1000,
fallback_name="Near Detection",
translation_key="near_detection"
)
.number(
HumanPresenceSensorManufCluster.AttributeDefs.far_detection.name,
HumanPresenceSensorManufCluster.cluster_id,
step=1,
min_value=0,
max_value=1000,
fallback_name="Far Detection",
translation_key="far_detection"
)
.enum(
HumanPresenceSensorManufCluster.AttributeDefs.human_motion_state.name,
HumanMotionState,
HumanPresenceSensorManufCluster.cluster_id,
entity_platform=EntityPlatform.SENSOR,
entity_type=EntityType.STANDARD,
fallback_name="Human Motion State",
translation_key="human_motion_state"
)
.number(
HumanPresenceSensorManufCluster.AttributeDefs.presence_time.name,
HumanPresenceSensorManufCluster.cluster_id,
step=1,
min_value=10,
max_value=28800,
fallback_name="Presence Time",
translation_key="presence_time"
)
.switch(
HumanPresenceSensorManufCluster.AttributeDefs.indicator.name,
HumanPresenceSensorManufCluster.cluster_id,
fallback_name="Indicator",
translation_key="indicator"
)

.binary_sensor(

HumanPresenceSensorManufCluster.AttributeDefs.reset_setting.name,

HumanPresenceSensorManufCluster.cluster_id

)

.enum(
    HumanPresenceSensorManufCluster.AttributeDefs.motion_detection_mode.name,
    MotionDetectionMode,
    HumanPresenceSensorManufCluster.cluster_id,
    fallback_name="Motion Detection Mode",
    translation_key="motion_detection_mode"
)
.number(
    HumanPresenceSensorManufCluster.AttributeDefs.motion_detection_sen.name,
    HumanPresenceSensorManufCluster.cluster_id,
    step=1,
    min_value=0,
    max_value=10,
    fallback_name="Motion Detection Sensitivity",
    translation_key="motion_detection_sen"
)

.number(

HumanPresenceSensorManufCluster.AttributeDefs.ver.name,

HumanPresenceSensorManufCluster.cluster_id,

step=1,

min_value=0,

max_value=10

)

.add_to_registry()

)

Thanks for the good work.
After adding the quirk, new options appeared and everything seems to be ok, but the sensor, despite setting 10s presence, goes into idle mode after about 90 seconds. Can this be fixed?

ps_quirk

@nerumo
Copy link

nerumo commented Dec 1, 2024

it's not possible to go below 30s (but that worked for me). Finally, that pdf document from @txip gave me the inputs, which finally work:

image

Far Detection: 500
Mode: Only PIR
Motion Detection Sensitivity: 5
Presence Time: 30
Sensitivity: 6

@robert4y
Copy link

robert4y commented Dec 3, 2024 via email

@vinzent
Copy link
Author

vinzent commented Dec 3, 2024

@robert4y IMHO this is a "presence" sensor not a "motion" sensor. it will report "presence" or "no presence" not motion/no-motion. For me 30s is Ok for presence in my rooms.

@abacao
Copy link

abacao commented Dec 20, 2024

does occupancy works on your side?

@rdehuyss
Copy link

I have just tested this quirk and it 'works' reasonably. The parameters can be edited but for one of my sensors I have the impression the change is not applied to the device itself.

It adds features like human_motion_state where I can see large_move and small_move. I have not seen breathe. I put it under our television to see whether it would work correctly while watching a one hour show but it did change state to none but this is more related to the hardware I think.

Any reason why no PR is created to add the Quirk to Zigpy? It would close issue zigpy/zha-device-handlers#3125.

@marvelloard
Copy link

marvelloard commented Feb 9, 2025

I added the quirk above, but still no configuration available.
Edit: Works after another reboot just fine!

@xionc666
Copy link

xionc666 commented Jun 10, 2025

Do you ever have battery states other than 50% or 100%? I see the battery level jumping between 50% and 100%, back and forth, in the last 3 months. I guess one day the battery will just die.

@darth-hp
Copy link

image Last 30 days, where as a drop to 10% occured the first time three days ago. Nevertheless - the report is weird

@mikosoft83
Copy link

I confirm, although I don't have battery history, I don't think I've seen other levels than 100% and 50%.

@BambamNZ
Copy link

I'm currently seeing either 100% or 10%, did start off with 100% and 50% and a few other reading in between.
It is now just either 100% or 10%

Graph is from 1st Jan to June 12th

image

@marvelloard
Copy link

You're right, I get 100% battery all the time, which might be incorrect.

@txip
Copy link

txip commented Jun 15, 2025

The device doesn't expose battery percentage, just three states: low (0%), medium (50%), full (100%).

@darth-hp
Copy link

Can't be. Check my chart above

@xionc666
Copy link

@darth-hp Left part of your chart are historical records averaged out of multiple measures, so e.g. 100%,100%,50% giving 83%.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment