-
-
Save vinzent/2cd645b848fd3b6a0c3e5762956ec89f to your computer and use it in GitHub Desktop.
""" | |
* 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() | |
) |
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.
I added the quirk above, but still no configuration available.
Edit: Works after another reboot just fine!
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.
I confirm, although I don't have battery history, I don't think I've seen other levels than 100% and 50%.
You're right, I get 100% battery all the time, which might be incorrect.
The device doesn't expose battery percentage, just three states: low (0%), medium (50%), full (100%).
Can't be. Check my chart above
@darth-hp Left part of your chart are historical records averaged out of multiple measures, so e.g. 100%,100%,50% giving 83%.
does occupancy works on your side?