-
-
Save vinzent/2194a8ef531d8e8ec3bc17ea17eab0e9 to your computer and use it in GitHub Desktop.
"""Luminance motion sensor ZG-204ZS""" | |
from typing import Dict, Final | |
from zigpy.quirks.v2 import QuirkBuilder | |
import zigpy.types as t | |
from zigpy.zcl.clusters.measurement import IlluminanceMeasurement, OccupancySensing | |
from zigpy.zcl.clusters.security import IasZone | |
from zigpy.zcl.clusters.general import Identify | |
from zigpy.zcl.foundation import ZCLAttributeDef | |
from zhaquirks.tuya import NoManufacturerCluster, TuyaLocalCluster, TuyaPowerConfigurationCluster2AAA | |
from zhaquirks.tuya.mcu import ( | |
DPToAttributeMapping, | |
TuyaMCUCluster, | |
) | |
class TuyaOccupancySensing(OccupancySensing, TuyaLocalCluster): | |
"""Tuya local OccupancySensing cluster.""" | |
class TuyaIlluminanceMeasurement(IlluminanceMeasurement, TuyaLocalCluster): | |
"""Tuya local IlluminanceMeasurement cluster.""" | |
class PirState(t.enum8): | |
"""Pir state""" | |
pir = 0x00 | |
none = 0x01 | |
@staticmethod | |
def to_occupancy(x): | |
""" Map PIR state to occupancy enum. Actually the PirState is Occupancy inverted.""" | |
if x == PirState.pir: | |
return OccupancySensing.Occupancy.Occupied | |
return OccupancySensing.Occupancy.Unoccupied | |
class PirSensitivity(t.enum8): | |
"""Sensitivity level enum.""" | |
LOW = 0x00 | |
MEDIUM = 0x01 | |
HIGH = 0x02 | |
class PirTime(t.enum8): | |
"""Keep time""" | |
_10_SEC = 0x00 | |
_30_SEC = 0x01 | |
_60_SEC = 0x02 | |
_120_SEC = 0x03 | |
class LuminanceMotionManufCluster(NoManufacturerCluster, TuyaMCUCluster): | |
"""Tuya Manufacturer cluster""" | |
# 1 "PIR state" pir_state Enum pir|none | |
# 4 "Battery level" battery_percentage Integer unit=%,min=0,max=100,scale=0,step=1 | |
# 9 "PIR Sensitivity" pir_sensitivity Enum low|middle|high | |
# 10 "Keep time" pir_time Enum 10s|30s|60s|120s | |
# 12 "Illuminance value" illuminance_value Integer unit=lux,min=0,max=10000,scale=0,step=1 | |
# 102 "Light Sense Interval Time(Version 2.0.1 and above)" interval_time Integer unit="Min",min=1,max=720,scale=0,step=1 | |
class AttributeDefs(TuyaMCUCluster.AttributeDefs): | |
"""Manufacturer specific attribute definitions""" | |
# Mapped to OccupancySensing cluster | |
# pir_state: Final = ZCLAttributeDef( | |
# id=0x0001, # DP 9 | |
# type=PirState, | |
# is_manufacturer_specific=True, | |
# ) | |
battery_percentage: Final = ZCLAttributeDef( | |
id=0x0004, # DP 4 | |
type=t.uint16_t, | |
is_manufacturer_specific=True, | |
) | |
pir_sensitivity: Final = ZCLAttributeDef( | |
id=0x0009, # DP 9 | |
type=PirSensitivity, | |
is_manufacturer_specific=True, | |
) | |
pir_time: Final = ZCLAttributeDef( | |
id=0x0010, # DP 10 | |
type=PirTime, | |
is_manufacturer_specific=True, | |
) | |
illuminance_value: Final = ZCLAttributeDef( | |
id=0x0012, # DP 12 | |
type=t.uint16_t, | |
is_manufacturer_specific=True, | |
) | |
interval_time: Final = ZCLAttributeDef( | |
id=0x0102, # DP 102 | |
type=t.uint16_t, | |
is_manufacturer_specific=True, | |
) | |
dp_to_attribute: Dict[int, DPToAttributeMapping] = { | |
1: DPToAttributeMapping( | |
TuyaOccupancySensing.ep_attribute, | |
"occupancy", | |
converter=PirState.to_occupancy | |
), | |
4: DPToAttributeMapping( | |
TuyaMCUCluster.ep_attribute, | |
"battery_percentage", | |
), | |
9: DPToAttributeMapping( | |
TuyaMCUCluster.ep_attribute, | |
"pir_sensitivity", | |
converter=PirSensitivity | |
), | |
10: DPToAttributeMapping( | |
TuyaMCUCluster.ep_attribute, | |
"pir_time", | |
converter=PirTime | |
), | |
12: DPToAttributeMapping( | |
TuyaMCUCluster.ep_attribute, | |
"illuminance_value", | |
), | |
102: DPToAttributeMapping( | |
TuyaMCUCluster.ep_attribute, | |
"interval_time", | |
), | |
} | |
data_point_handlers = { | |
1: "_dp_2_attr_update", | |
4: "_dp_2_attr_update", | |
9: "_dp_2_attr_update", | |
10: "_dp_2_attr_update", | |
12: "_dp_2_attr_update", | |
102: "_dp_2_attr_update", | |
} | |
( | |
QuirkBuilder("_TZE200_3towulqd", "TS0601") | |
.skip_configuration() | |
.removes(IasZone.cluster_id) | |
.removes(Identify.cluster_id) | |
.adds(LuminanceMotionManufCluster) | |
.adds(TuyaOccupancySensing) | |
.replaces(TuyaPowerConfigurationCluster2AAA) | |
.replaces(TuyaIlluminanceMeasurement) | |
.enum( | |
LuminanceMotionManufCluster.AttributeDefs.pir_time.name, | |
PirTime, | |
LuminanceMotionManufCluster.cluster_id, | |
fallback_name="PIR time", | |
translation_key="pir_time" | |
) | |
.enum( | |
LuminanceMotionManufCluster.AttributeDefs.pir_sensitivity.name, | |
PirSensitivity, | |
LuminanceMotionManufCluster.cluster_id, | |
fallback_name="PIR sensitivity", | |
translation_key="pir_sensitivity" | |
) | |
# Exception with unit="m" seen with HA 2024.6.0 + 2024.5.x | |
.number( | |
LuminanceMotionManufCluster.AttributeDefs.interval_time.name, | |
LuminanceMotionManufCluster.cluster_id, | |
step=1, | |
min_value=1, | |
max_value=720, | |
fallback_name="Interval time", | |
translation_key="interval_time" | |
) | |
.add_to_registry() | |
) |
@jdm09 can you paste your quirk correctly formatted? I’m trying to make it work with the same motion sensor as yours.
@jdm09 can you paste your quirk correctly formatted? I’m trying to make it work with the same motion sensor as yours.
@wkrzyzanowski Sure, would really appreciate this!
here is the Version, which is working actually, but this one does not update the illuminance and battery percentage.
I can not find the wrong part.
Thanks for your support!
Modified Quirk for round PIR
#import math
from typing import Dict, Final
from zigpy.quirks.v2 import QuirkBuilder
import zigpy.types as t
from zigpy.zcl.clusters.measurement import IlluminanceMeasurement, OccupancySensing
from zigpy.zcl.clusters.security import IasZone
from zigpy.zcl.clusters.general import Identify
from zigpy.zcl.foundation import ZCLAttributeDef
from zhaquirks.tuya import NoManufacturerCluster, TuyaLocalCluster, TuyaPowerConfigurationCluster2AAA
from zhaquirks.tuya.mcu import (
DPToAttributeMapping,
TuyaMCUCluster,
TuyaPowerConfigurationCluster,
)
class TuyaOccupancySensing(OccupancySensing, TuyaLocalCluster):
"""Tuya local OccupancySensing cluster."""
class TuyaIlluminanceMeasurement(IlluminanceMeasurement, TuyaLocalCluster):
"""Tuya local IlluminanceMeasurement cluster."""
class PirState(t.enum8):
"""Pir state"""
pir = 0x00
none = 0x01
@staticmethod
def to_occupancy(x):
""" Map PIR state to occupancy enum. Actually the PirState is Occupancy inverted."""
if x == PirState.pir:
return OccupancySensing.Occupancy.Occupied
return OccupancySensing.Occupancy.Unoccupied
class PirSensitivity(t.enum8):
"""Sensitivity level enum."""
LOW = 0x00
MEDIUM = 0x01
HIGH = 0x02
class PirTime(t.enum8):
"""Keep time"""
_10_SEC = 0x00
_30_SEC = 0x01
_60_SEC = 0x02
_120_SEC = 0x03
#def illuminance_converter(x):
# """Converter-Methode für die mathematische Berechnung"""
# if x != 0:
# return 10000 * math.log10(x) + 1
# return 0
class LuminanceMotionManufCluster(NoManufacturerCluster, TuyaMCUCluster):
"""Tuya Manufacturer cluster"""
# 1 "PIR state" pir_state Enum pir|none
# 4 "Battery level" battery_percentage Integer unit=%,min=0,max=100,scale=0,step=1
# 9 "PIR Sensitivity" pir_sensitivity Enum low|middle|high
# 10 "Keep time" pir_time Enum 10s|30s|60s|120s
# 12 "Illuminance value" illuminance_value Integer unit=lux,min=0,max=10000,scale=0,step=1
# 102 "Light Sense Interval Time(Version 2.0.1 and above)" interval_time Integer unit="Min",min=1,max=720,scale=0,step=1
class AttributeDefs(TuyaMCUCluster.AttributeDefs):
"""Manufacturer specific attribute definitions"""
# Mapped to OccupancySensing cluster
# pir_state: Final = ZCLAttributeDef(
# id=0x0001, # DP 9
# type=PirState,
# is_manufacturer_specific=True,
# )
battery_percentage_remaining: Final = ZCLAttributeDef(
id=0x0004, # DP 4
type=t.uint16_t,
is_manufacturer_specific=True,
)
pir_sensitivity: Final = ZCLAttributeDef(
id=0x0009, # DP 9
type=PirSensitivity,
is_manufacturer_specific=True,
)
pir_time: Final = ZCLAttributeDef(
id=0x0010, # DP 10
type=PirTime,
is_manufacturer_specific=True,
)
illuminance_value: Final = ZCLAttributeDef(
id=0x0012, # DP 12
type=t.uint16_t,
is_manufacturer_specific=True,
)
interval_time: Final = ZCLAttributeDef(
id=0x0102, # DP 102
type=t.uint16_t,
is_manufacturer_specific=True,
)
dp_to_attribute: Dict[int, DPToAttributeMapping] = {
1: DPToAttributeMapping(
TuyaOccupancySensing.ep_attribute,
"occupancy",
converter=PirState.to_occupancy
),
4: DPToAttributeMapping(
TuyaPowerConfigurationCluster.ep_attribute,
"battery_percentage_remaining"
),
9: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"pir_sensitivity",
converter=PirSensitivity
),
10: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"pir_time",
converter=PirTime
),
12: DPToAttributeMapping(
TuyaIlluminanceMeasurement.ep_attribute,
"illuminance_value",
#converter=illuminance_converter
),
102: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"interval_time"
),
}
data_point_handlers = {
1: "_dp_2_attr_update",
4: "_dp_2_attr_update",
9: "_dp_2_attr_update",
10: "_dp_2_attr_update",
12: "_dp_2_attr_update",
102: "_dp_2_attr_update",
}
(
QuirkBuilder("_TZE200_3towulqd", "TS0601")
.skip_configuration()
.removes(IasZone.cluster_id)
.removes(Identify.cluster_id)
.adds(LuminanceMotionManufCluster)
.adds(TuyaOccupancySensing)
.replaces(TuyaPowerConfigurationCluster)
.replaces(TuyaPowerConfigurationCluster2AAA)
.replaces(TuyaIlluminanceMeasurement)
.enum(
LuminanceMotionManufCluster.AttributeDefs.pir_time.name,
PirTime,
LuminanceMotionManufCluster.cluster_id,
fallback_name="PIR time",
translation_key="pir_time"
)
.enum(
LuminanceMotionManufCluster.AttributeDefs.pir_sensitivity.name,
PirSensitivity,
LuminanceMotionManufCluster.cluster_id,
fallback_name="PIR sensitivity",
translation_key="pir_sensitivity"
)
# Exception with unit="m" seen with HA 2024.6.0 + 2024.5.x
.number(
LuminanceMotionManufCluster.AttributeDefs.interval_time.name,
LuminanceMotionManufCluster.cluster_id,
step=1,
min_value=1,
max_value=720,
fallback_name="Interval time",
translation_key="interval_time"
)
.add_to_registry()
)
Thank you! Will take a look 👌
I’m trying to make this to work:
Tuya Zigbee Human Motion Sensor
https://a.aliexpress.com/_EGQq8gk
This is motion + illuminace sensor and I need to make configurable detection intervals.
@wkrzyzanowski
What i recently noticed is a small difference between two sorts of this ZB PIR you mentioned above:
1. this Signature seems to work with this quirk
{
"node_descriptor": {
"logical_type": 2,
"complex_descriptor_available": 0,
"user_descriptor_available": 0,
"reserved": 0,
"aps_flags": 0,
"frequency_band": 8,
"mac_capability_flags": 128,
"manufacturer_code": 4417,
"maximum_buffer_size": 66,
"maximum_incoming_transfer_size": 66,
"server_mask": 10752,
"maximum_outgoing_transfer_size": 66,
"descriptor_capability_field": 0
},
"endpoints": {
"1": {
"profile_id": "0x0104",
"device_type": "0x0402",
"input_clusters": [
"0x0000",
"0x0001",
"0x0400",
"0x0406",
"0xe000",
"0xe002",
"0xee00",
"0xef00"
],
"output_clusters": []
}
},
"manufacturer": "_TZE200_3towulqd",
"model": "TS0601",
"class": "zigpy.quirks.v2.CustomDeviceV2"
}
2. this slightly different Signature is also from the same Model but is not working:
{
"node_descriptor": {
"logical_type": 2,
"complex_descriptor_available": 0,
"user_descriptor_available": 0,
"reserved": 0,
"aps_flags": 0,
"frequency_band": 8,
"mac_capability_flags": 128,
"manufacturer_code": 4417,
"maximum_buffer_size": 66,
"maximum_incoming_transfer_size": 66,
"server_mask": 10752,
"maximum_outgoing_transfer_size": 66,
"descriptor_capability_field": 0
},
"endpoints": {
"1": {
"profile_id": "0x0104",
"device_type": "0x0402",
"input_clusters": [
"0x0000",
"0x0001",
"0x0400",
"0x0406",
"0xef00"
],
"output_clusters": [
"0x000a",
"0x0019"
]
}
},
"manufacturer": "_TZE200_3towulqd",
"model": "TS0601",
"class": "zigpy.quirks.v2.CustomDeviceV2"
}
Actually i have both of these PIRs in my ZHA Network.
Seems that there are slightly different Models with the same Manufacturer and Model Signature.
But sorry, i have no idea on how to identify and treat these differences with the quirk.
Hi,
i managed it to get back working for me. If someone does have the same issue as me, here is the working quirk v2 for the Device with Signature: _TZE200_3towulqd but the round tiny version
PIR round with Lux
Modified Quirk for round PIR
Thanks again for your very good quirk v2!!