Closed illigtr closed 2 days ago
For some reason Firefox has blocked me from paste/dropping pictures in the original link.
Update:
I have found a quirk which appears to work flawlessly with this model sensor in open issue 2974. https://github.com/zigpy/zha-device-handlers/issues/2974
Although the quirk is for the ZY-M100 sensor which looks different than my sensor, the quirk works, nonetheless.
Please consider adding this quirk (from issue 2974) and disregard the older quirk referenced in this issue.
"""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: [
("_TZE204_ztqnh5cg", "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],
},
}
}
There hasn't been any activity on this issue recently. Due to the high number of incoming GitHub notifications, we have to clean some of the old issues, as many of them have already been resolved with the latest updates. Please make sure to update to the latest version and check if that solves the issue. Let us know if that works for you by adding a comment 👍 This issue has now been marked as stale and will be closed if no further activity occurs. Thank you for your contributions.
Problem description
I have this sensor, https://github.com/home-assistant/core/issues/url It was working fine with HA ZHA using a custom quirk I found. However, the April updates to HA (which I assume also included updates to ZHA broke this integration) I have have tried, hopelessly to stitch together my original quirk with the one in Tuya/ts0601_motion.py... but nothing works. If it works at all, it shows only occupancy entity, but it doesn't even work. Here is the original WORKING quirk from March 2024...
Solution description
Please update ts0601_motion.py to support this model.
Screenshots/Video
Screenshots/Video
[Paste/upload your media here]Device signature
Device signature
```json { "node_descriptor": "NodeDescriptor(logical_type=Diagnostic information
Diagnostic information
```json [Paste the diagnostic information here] ```Logs
Logs
```python [Paste the logs here] ```Custom quirk
Custom quirk
```python """Tuya mmw radar occupancy 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 ) from zigpy.zcl.clusters.security import IasZone from zhaquirks import Bus, LocalDataCluster, MotionOnEvent from zhaquirks.const import ( DEVICE_TYPE, ENDPOINTS, INPUT_CLUSTERS, MODEL, MOTION_EVENT, OUTPUT_CLUSTERS, PROFILE_ID, ) from zhaquirks.tuya import ( NoManufacturerCluster, TuyaLocalCluster, TuyaNewManufCluster, ) from zhaquirks.tuya.mcu import ( TuyaDPType, DPToAttributeMapping, TuyaAttributesCluster, TuyaMCUCluster, ) class TuyaMmwRadarSelfTest(t.enum8): """Mmw radar self test values.""" TESTING = 0 TEST_SUCCESS = 1 TEST_FAILURE = 2 OTHER = 3 COMM_FAULT = 4 RADAR_FAULT = 5 class TuyaOccupancySensing(OccupancySensing, TuyaLocalCluster): """Tuya local OccupancySensing cluster.""" class TuyaIlluminanceMeasurement(IlluminanceMeasurement, TuyaLocalCluster): """Tuya local IlluminanceMeasurement cluster.""" 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 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 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 TuyaMmwRadarCluster(NoManufacturerCluster, TuyaMCUCluster): """Mmw radar cluster.""" attributes = TuyaMCUCluster.attributes.copy() attributes.update( { # ramdom attribute IDs 0xEF01: ("occupancy", t.uint32_t, True), 0xEF02: ("sensitivity", t.uint32_t, True), 0xEF03: ("min_range", t.uint32_t, True), 0xEF04: ("max_range", t.uint32_t, True), 0xEF06: ("self_test", TuyaMmwRadarSelfTest, True), 0xEF09: ("target_distance", t.uint32_t, True), 0xEF65: ("detection_delay", t.uint32_t, True), 0xEF66: ("fading_time", t.uint32_t, True), 0xEF67: ("cli", t.CharacterString, True), 0xEF68: ("illuminance", t.uint32_t, True), } ) dp_to_attribute: Dict[int, DPToAttributeMapping] = { 1: DPToAttributeMapping( TuyaOccupancySensing.ep_attribute, "occupancy", dp_type=TuyaDPType.BOOL, ), 2: DPToAttributeMapping( TuyaMmwRadarSensitivity.ep_attribute, "present_value", dp_type=TuyaDPType.VALUE, ), 3: DPToAttributeMapping( TuyaMmwRadarMinRange.ep_attribute, "present_value", dp_type=TuyaDPType.VALUE, endpoint_id=2, #converter=lambda x: x / 100, #dp_converter=lambda x: x * 100, ), 4: DPToAttributeMapping( TuyaMmwRadarMaxRange.ep_attribute, "present_value", dp_type=TuyaDPType.VALUE, endpoint_id=3, #converter=lambda x: x / 100, #dp_converter=lambda x: x * 100, ), 6: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "self_test", dp_type=TuyaDPType.ENUM, ), 9: DPToAttributeMapping( TuyaMmwRadarTargetDistance.ep_attribute, "present_value", #converter=lambda x: x / 100, dp_type=TuyaDPType.VALUE, ), 101: DPToAttributeMapping( TuyaMmwRadarDetectionDelay.ep_attribute, "present_value", dp_type=TuyaDPType.VALUE, converter=lambda x: x * 100, dp_converter=lambda x: x // 100, endpoint_id=4, ), 102: DPToAttributeMapping( TuyaMmwRadarFadingTime.ep_attribute, "present_value", dp_type=TuyaDPType.VALUE, converter=lambda x: x * 100, dp_converter=lambda x: x // 100, endpoint_id=5, ), 103: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "cli", dp_type=TuyaDPType.STRING, ), 104: DPToAttributeMapping( TuyaIlluminanceMeasurement.ep_attribute, "measured_value", dp_type=TuyaDPType.VALUE, converter=lambda x: math.log10(x) * 10000 + 1 if x > 0 else 0, ), } data_point_handlers = { 1: "_dp_2_attr_update", 2: "_dp_2_attr_update", 3: "_dp_2_attr_update", 4: "_dp_2_attr_update", 6: "_dp_2_attr_update", 9: "_dp_2_attr_update", 101: "_dp_2_attr_update", 102: "_dp_2_attr_update", 103: "_dp_2_attr_update", 104: "_dp_2_attr_update", } class TuyaMmwRadarOccupancy(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": [ ("_TZE204_ztqnh5cg", "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, TuyaMmwRadarCluster, TuyaIlluminanceMeasurement, TuyaOccupancySensing, TuyaMmwRadarTargetDistance, TuyaMmwRadarSensitivity, ], OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id], }, 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, ], OUTPUT_CLUSTERS: [], }, 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: [], }, } } ```Additional information
No response