-
Notifications
You must be signed in to change notification settings - Fork 728
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Device Support Request] ZY-M100 Series Presence Sensor (TS0601 by _TZE204_ijxvkhd0) #2852
Comments
If there's an easy way to convert the implementation into a quirk or something, zigbee2mqtt has support, even though it doesn't seem to work flawlessly yet. |
Here is the data from Manage Zigbee Device -> Signature. It looks identical to the motion sensor already included in the latest ZHA code with class MmwRadarMotionGPP in ts0601_motion.py. Same input clusters and output clusters for endpoints 1 and 242.
|
I added _TZE204_ijxvkhd0 to the MmwRadarMotionGPP, same as used by _TZE204_qasjif9e ( #2510 ). I get a sensor for Occupancy but it is always set to Clear and doesn't change. No other sensors or controls that I can see. I can get light sensor data from TuyaIlluminanceMeasurement (Endpoint id: 1, Id: 0x0400, Type: in), attribute measured_value (id: 0x0000). It changes the value read when I cover the sensor. All the occupancy sensing output is None (from TuyaOccupancySensing (Endpoint id: 1, Id: 0x0406, Type: in)), except for occupancy (0x0000) which is always zero (0). MmwRadarManufCluster, mcu_version returns 1.1.8. Edit: For some unclear reason, a few restarts of HA later (finishing up other upgrade changes), I now have a sensor for Illuminance. Occupancy is still always Clear (occupancy attribute reading 0). I do however with manual reads of dp_105 get either 0x0, 0x1 or 0x2, which according to the zigbee2mqtt implementation is for Presence states.
|
Occupancy seems to work (can show Clear and Detected as expected) if I switch occupancy from using dp_1 attribute, to using dp_112. It's used in the zigbee2mqtt implementation for "presence" true/false. I don't know how to create a custom sensor for the presence_state type attribute. |
I ordered several of these sensors for testing purposes and discovered they didn't work out of the box since I use ZHA. I don't want to migrate to Zigbee2MQTT just because of this issue. @logan893, can you share how you added '_TZE204_ijxvkhd0' to the MmwRadarMotionGPP? I would like to test this. Thank you |
@visata I don't know if it's all proper, but this is what I have changed and added so far.
|
@logan893 I'm adding this quirk from here https://fixtse.com/blog/zy-m100-full-zha-support Will let you know if it works. |
@visata Looks good. Have you gotten the "target distance" to display as a sensor? |
I cobbled together a custom quirk based on fixtse's quirk ( https://fixtse.com/blog/zy-m100-full-zha-support ; https://gist.githubusercontent.com/fixtse/b95753b84c34b45f49b3116d23b66342/raw/0f84bd6e9b6c174970c7b5fc21078d4b4da06a15/TZE204_ijxvkhd0_e5m9c5hl.py ) with some influence from #2525 (comment) and my own additions. I haven't seen that there would be any "fade time remaining" parameter for _TZE204_ijxvkhd0, but I have no clue where to begin digging. I added an Iaszone motion sensor to separately track the motion detected (seems to be a fixed fade time of roughly 5-6 seconds). Distance to target is via a pressure sensor, showing cm to target with unit hPa. (this custom quirk also pasted in initial problem description) ## originally based on
### https://fixtse.com/blog/zy-m100-full-zha-support
### ( https://gist.githubusercontent.com/fixtse/b95753b84c34b45f49b3116d23b66342/raw/0f84bd6e9b6c174970c7b5fc21078d4b4da06a15/TZE204_ijxvkhd0_e5m9c5hl.py )
## with inspiration from
### https://github.com/zigpy/zha-device-handlers/pull/2525#issuecomment-1826881992
## and my own changes and additions
import math
from typing import Dict
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,
Ota,
Scenes,
Time,
)
from zigpy.zcl.clusters.measurement import (
IlluminanceMeasurement,
OccupancySensing,
PressureMeasurement,
)
from zigpy.zcl.clusters.security import IasZone, ZoneType
from zhaquirks import Bus, LocalDataCluster
from zhaquirks.const import (
DEVICE_TYPE,
ENDPOINTS,
INPUT_CLUSTERS,
MODELS_INFO,
OUTPUT_CLUSTERS,
PROFILE_ID,
CLUSTER_COMMAND,
ZONE_STATUS_CHANGE_COMMAND,
ON,
OFF,
)
from zhaquirks.tuya import (
NoManufacturerCluster,
TuyaLocalCluster,
TuyaNewManufCluster,
)
from zhaquirks.tuya.mcu import (
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 PresenceMotionEnum(t.enum8):
"""Presence and motion enum."""
NONE = 0x00
PRESENCE = 0x01
MOTION = 0x02
class TuyaMmwRadarTargetDistanceAsPressureMeasurement(PressureMeasurement, TuyaLocalCluster): # result in centimeteres expressed as hPa
"""Target Distance."""
class TuyaMmwRadarMotionSensitivity(TuyaAttributesCluster, AnalogOutput):
"""AnalogOutput cluster for motion sensitivity."""
_CONSTANT_ATTRIBUTES = {
AnalogOutput.AttributeDefs.description.id: "motion sensitivity",
AnalogOutput.AttributeDefs.min_present_value.id: 1,
AnalogOutput.AttributeDefs.max_present_value.id: 9,
AnalogOutput.AttributeDefs.resolution.id: 1,
}
class TuyaMmwRadarPresenceSensitivity(TuyaAttributesCluster, AnalogOutput):
"""AnalogOutput cluster for presence sensitivity."""
_CONSTANT_ATTRIBUTES = {
AnalogOutput.AttributeDefs.description.id: "presence sensitivity",
AnalogOutput.AttributeDefs.min_present_value.id: 1,
AnalogOutput.AttributeDefs.max_present_value.id: 9,
AnalogOutput.AttributeDefs.resolution.id: 1,
}
class TuyaMmwRadarFadingTime(TuyaAttributesCluster, AnalogOutput):
"""AnalogOutput cluster for fading time."""
_CONSTANT_ATTRIBUTES = {
AnalogOutput.AttributeDefs.description.id: "fading time",
AnalogOutput.AttributeDefs.min_present_value.id: 1,
AnalogOutput.AttributeDefs.max_present_value.id: 1000,
AnalogOutput.AttributeDefs.resolution.id: 1, # Resolution 1 second
AnalogOutput.AttributeDefs.engineering_units.id: 73, # 73 defines seconds, the expected unit
}
class TuyaMmwRadarMaxRange(TuyaAttributesCluster, AnalogOutput):
"""AnalogOutput cluster for max range."""
_CONSTANT_ATTRIBUTES = {
AnalogOutput.AttributeDefs.description.id: "max detection range",
AnalogOutput.AttributeDefs.min_present_value.id: 150, # min allowed = 150
AnalogOutput.AttributeDefs.max_present_value.id: 550, # max allowed = 550
AnalogOutput.AttributeDefs.resolution.id: 100, #resolution = 100 centermeters (snaps back to 100 cm intervals between 150 and 550)
AnalogOutput.AttributeDefs.engineering_units.id: 118, # 118 defines centimeters, the expected unit
}
class TuyaOccupancySensing(OccupancySensing, TuyaLocalCluster):
"""Tuya local OccupancySensing cluster."""
class TuyaIlluminanceMeasurement(IlluminanceMeasurement, TuyaLocalCluster):
"""Tuya local IlluminanceMeasurement cluster."""
class TuyaOccupancyMotionSensing(OccupancySensing, TuyaLocalCluster):
"""Tuya local OccupancySensing cluster for motion state."""
class TuyaMmwRadarMotionAnalogInputCluster(TuyaLocalCluster, AnalogInput):
"""Analog input cluster, only used to relay motion state information to Iaszone motion sensor."""
cluster_id = AnalogInput.cluster_id
def __init__(self, *args, **kwargs):
"""Init."""
super().__init__(*args, **kwargs)
def _update_attribute(self, attrid, value):
super()._update_attribute(attrid, value)
if attrid == AnalogInput.AttributeDefs.present_value.id: # 0x55 = "present_value" for AnalogInput
if value == PresenceMotionEnum.MOTION:
self.endpoint.device.motion_bus.listener_event("_turn_on")
else:
self.endpoint.device.motion_bus.listener_event("_turn_off")
class TuyaMmwRadarMotionSensing(LocalDataCluster, IasZone):
"""IasZone cluster for motion."""
_CONSTANT_ATTRIBUTES = {
IasZone.AttributeDefs.zone_type.id: ZoneType.Motion_Sensor, # 0x000D, # motion type
}
cluster_id = IasZone.cluster_id
def __init__(self, *args, **kwargs):
"""Init."""
super().__init__(*args, **kwargs)
self.endpoint.device.motion_bus.add_listener(self)
def _turn_off(self):
self.listener_event(
CLUSTER_COMMAND, 253, ZONE_STATUS_CHANGE_COMMAND, [OFF, 0, 0, 0]
)
def _turn_on(self):
self.listener_event(
CLUSTER_COMMAND, 254, ZONE_STATUS_CHANGE_COMMAND, [ON, 0, 0, 0]
)
class TuyaMmwRadarCluster(NoManufacturerCluster, TuyaMCUCluster):
"""Mmw radar cluster."""
attributes = TuyaMCUCluster.attributes.copy()
dp_to_attribute: Dict[int, DPToAttributeMapping] = {
103: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"cli",
),
104: DPToAttributeMapping(
TuyaIlluminanceMeasurement.ep_attribute,
"measured_value",
converter=lambda x: int(math.log10(x) * 10000 + 1) if x > 0 else int(0),
),
105: DPToAttributeMapping(
TuyaMmwRadarMotionAnalogInputCluster.ep_attribute,
"present_value",
converter=lambda x: PresenceMotionEnum(x),
endpoint_id=2,
),
106: DPToAttributeMapping(
TuyaMmwRadarMotionSensitivity.ep_attribute,
"present_value",
endpoint_id=6,
converter=lambda x: x if x < 10 else x / 10,
),
107: DPToAttributeMapping(
TuyaMmwRadarMaxRange.ep_attribute,
"present_value",
endpoint_id=3,
),
109: DPToAttributeMapping(
TuyaMmwRadarTargetDistanceAsPressureMeasurement.ep_attribute,
"measured_value",
),
110: DPToAttributeMapping(
TuyaMmwRadarFadingTime.ep_attribute,
"present_value",
endpoint_id=5,
),
111: DPToAttributeMapping(
TuyaMmwRadarPresenceSensitivity.ep_attribute,
"present_value",
endpoint_id=7,
converter=lambda x: x if x < 10 else x / 10,
),
112: DPToAttributeMapping(
TuyaOccupancySensing.ep_attribute,
"occupancy",
),
}
data_point_handlers = {
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",
109: "_dp_2_attr_update",
110: "_dp_2_attr_update",
111: "_dp_2_attr_update",
112: "_dp_2_attr_update",
}
class TuyaMmwRadarOccupancy(CustomDevice):
"""Millimeter wave occupancy sensor."""
def __init__(self, *args, **kwargs):
"""Init device."""
self.motion_bus = Bus()
super().__init__(*args, **kwargs)
signature = {
# endpoint=1, profile=260, device_type=81, device_version=1,
# input_clusters=[0, 4, 5, 61184], output_clusters=[25, 10]
MODELS_INFO: [
("_TZE204_ijxvkhd0", "TS0601"),
("_TZE204_e5m9c5hl", "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,
TuyaMmwRadarCluster,
TuyaIlluminanceMeasurement,
TuyaOccupancySensing,
TuyaMmwRadarTargetDistanceAsPressureMeasurement,
],
OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
},
2: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.OCCUPANCY_SENSOR,
INPUT_CLUSTERS: [
TuyaMmwRadarMotionAnalogInputCluster,
TuyaMmwRadarMotionSensing,
],
OUTPUT_CLUSTERS: [],
},
3: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
INPUT_CLUSTERS: [
TuyaMmwRadarMaxRange,
],
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: [
TuyaMmwRadarMotionSensitivity,
],
OUTPUT_CLUSTERS: [],
},
7: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
INPUT_CLUSTERS: [
TuyaMmwRadarPresenceSensitivity,
],
OUTPUT_CLUSTERS: [],
},
242: {
PROFILE_ID: 41440,
DEVICE_TYPE: 97,
INPUT_CLUSTERS: [],
OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
},
}
} |
Hi @logan893 , it works like a charm for _TZE204_ijxvkhd0. Thanks for sharing such a pearl :) Thank you. |
This worked wonders for me! |
Edit: nm ... PEBKAC error |
struggling to get it to pick up my model (_TZE204_qasjif9e). I've added that to the sig part of the py script and reloaded but it just keeps on picking up the same (what seems to be the default) edit ok sorted that, it was just an extra bracket in the code which somehow got in there. but now it works, it picks up illum values correctly (afaik) but always says clear to the presence/motion. and there doesn't appear to be a minimum distance value nor is there the exposed detected distance (not that detection is working right now). just seen that you mention the 24g model on yours, and mine is not. would that make a difference? |
I have a similar issue where the presence remains on detected and never changes to clear. |
anyone still caring about these devices? |
The distance is exactly the number of cm reported from the sensor. Minimum distance seems to be 150 cm (i.e. 150 hPa with the sensor), maximum 550 cm. |
The _TZE204_qasjif9e variant is the 5.8GHz version and it behaves quite differently. This quirk of mine is for the 24GHz model only. I also have that 5.8GHz variant with the same model as yours, and I too had problems with the default implementation. I haven't yet posted my updated quirk file that covers both, as I'm not sure it truly covers everything. With my variant of a quirk for _TZE204_qasjif9e you get access to a trigger-delay-timer and clear-timer, you get distance sensor and a min/max sense range, sensitivity, occupancy, and illumination. There is no extra active-motion sensor like there is for the 24GHz variant. Again, I don't know if this quirk covers it all. I'll see if I can post it later today. |
Is _TZE204_ztc6ggyl the ceiling mounted 5.8GHz variant? If it works the same like the wall mount variant then my updated quirk file (will post soon) should work for that also. Edit: Seems this device is quite different from the two I have, and my quirks won't work. |
@thefunkygibbon Try this one for _TZE204_qasjif9e. ## originally based on
### https://fixtse.com/blog/zy-m100-full-zha-support
### ( https://gist.githubusercontent.com/fixtse/b95753b84c34b45f49b3116d23b66342/raw/0f84bd6e9b6c174970c7b5fc21078d4b4da06a15/TZE204_ijxvkhd0_e5m9c5hl.py )
## with inspiration from
### https://github.com/zigpy/zha-device-handlers/pull/2525#issuecomment-1826881992
## and my own changes and additions
## Update: adding a quirk to support USB-C wall mount 5.8GHz variant (_TZE204_qasjif9e) based on zigbee2mqtt implementation (bonus support for _TZE204_ztqnh5cg)
## https://github.com/Koenkk/zigbee-herdsman-converters/blob/master/src/devices/tuya.ts
import math
from typing import Dict
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,
AnalogOutput,
Basic,
GreenPowerProxy,
Groups,
Ota,
Scenes,
Time,
)
from zigpy.zcl.clusters.measurement import (
IlluminanceMeasurement,
OccupancySensing,
PressureMeasurement,
)
from zigpy.zcl.clusters.security import IasZone, ZoneType
from zhaquirks import Bus, LocalDataCluster
from zhaquirks.const import (
DEVICE_TYPE,
ENDPOINTS,
INPUT_CLUSTERS,
MODELS_INFO,
OUTPUT_CLUSTERS,
PROFILE_ID,
CLUSTER_COMMAND,
ZONE_STATUS_CHANGE_COMMAND,
ON,
OFF,
)
from zhaquirks.tuya import (
NoManufacturerCluster,
TuyaLocalCluster,
TuyaNewManufCluster,
)
from zhaquirks.tuya.mcu import (
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 PresenceMotionEnum(t.enum8):
"""Presence and motion enum."""
NONE = 0x00
PRESENCE = 0x01
MOTION = 0x02
class TuyaMmwRadarTargetDistanceAsPressureMeasurement(PressureMeasurement, TuyaLocalCluster): # result in centimeteres expressed as hPa
"""Target Distance."""
class TuyaMmwRadarMotionSensitivity(TuyaAttributesCluster, AnalogOutput):
"""AnalogOutput cluster for motion sensitivity."""
_CONSTANT_ATTRIBUTES = {
AnalogOutput.AttributeDefs.description.id: "motion sensitivity",
AnalogOutput.AttributeDefs.min_present_value.id: 1,
AnalogOutput.AttributeDefs.max_present_value.id: 9,
AnalogOutput.AttributeDefs.resolution.id: 1,
}
class TuyaMmwRadarPresenceSensitivity(TuyaAttributesCluster, AnalogOutput):
"""AnalogOutput cluster for presence sensitivity."""
_CONSTANT_ATTRIBUTES = {
AnalogOutput.AttributeDefs.description.id: "presence sensitivity",
AnalogOutput.AttributeDefs.min_present_value.id: 1,
AnalogOutput.AttributeDefs.max_present_value.id: 9,
AnalogOutput.AttributeDefs.resolution.id: 1,
}
class TuyaMmwRadarFadingTime(TuyaAttributesCluster, AnalogOutput):
"""AnalogOutput cluster for fading time."""
_CONSTANT_ATTRIBUTES = {
AnalogOutput.AttributeDefs.description.id: "fading time",
AnalogOutput.AttributeDefs.min_present_value.id: 1,
AnalogOutput.AttributeDefs.max_present_value.id: 1000,
AnalogOutput.AttributeDefs.resolution.id: 1, # Resolution 1 second
AnalogOutput.AttributeDefs.engineering_units.id: 73, # 73 defines seconds, the expected unit
}
class TuyaMmwRadarMaxRange(TuyaAttributesCluster, AnalogOutput):
"""AnalogOutput cluster for max range."""
_CONSTANT_ATTRIBUTES = {
AnalogOutput.AttributeDefs.description.id: "max detection range",
AnalogOutput.AttributeDefs.min_present_value.id: 150, # min allowed = 150
AnalogOutput.AttributeDefs.max_present_value.id: 550, # max allowed = 550
AnalogOutput.AttributeDefs.resolution.id: 100, #resolution = 100 centermeters (snaps back to 100 cm intervals between 150 and 550)
AnalogOutput.AttributeDefs.engineering_units.id: 118, # 118 defines centimeters, the expected unit
}
class TuyaOccupancySensing(OccupancySensing, TuyaLocalCluster):
"""Tuya local OccupancySensing cluster."""
class TuyaIlluminanceMeasurement(IlluminanceMeasurement, TuyaLocalCluster):
"""Tuya local IlluminanceMeasurement cluster."""
class TuyaOccupancyMotionSensing(OccupancySensing, TuyaLocalCluster):
"""Tuya local OccupancySensing cluster for motion state."""
class TuyaMmwRadarMotionAnalogInputCluster(TuyaLocalCluster, AnalogInput):
"""Analog input cluster, only used to relay motion state information to Iaszone motion sensor."""
cluster_id = AnalogInput.cluster_id
def __init__(self, *args, **kwargs):
"""Init."""
super().__init__(*args, **kwargs)
def _update_attribute(self, attrid, value):
super()._update_attribute(attrid, value)
if attrid == AnalogInput.AttributeDefs.present_value.id: # 0x55 = "present_value" for AnalogInput
if value == PresenceMotionEnum.MOTION:
self.endpoint.device.motion_bus.listener_event("_turn_on")
else:
self.endpoint.device.motion_bus.listener_event("_turn_off")
class TuyaMmwRadarMotionSensing(LocalDataCluster, IasZone):
"""IasZone cluster for motion."""
_CONSTANT_ATTRIBUTES = {
IasZone.AttributeDefs.zone_type.id: ZoneType.Motion_Sensor, # 0x000D, # motion type
}
cluster_id = IasZone.cluster_id
def __init__(self, *args, **kwargs):
"""Init."""
super().__init__(*args, **kwargs)
self.endpoint.device.motion_bus.add_listener(self)
def _turn_off(self):
self.listener_event(
CLUSTER_COMMAND, 253, ZONE_STATUS_CHANGE_COMMAND, [OFF, 0, 0, 0]
)
def _turn_on(self):
self.listener_event(
CLUSTER_COMMAND, 254, ZONE_STATUS_CHANGE_COMMAND, [ON, 0, 0, 0]
)
class TuyaMmwRadarCluster(NoManufacturerCluster, TuyaMCUCluster):
"""Mmw radar cluster."""
attributes = TuyaMCUCluster.attributes.copy()
dp_to_attribute: Dict[int, DPToAttributeMapping] = {
103: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"cli",
),
104: DPToAttributeMapping(
TuyaIlluminanceMeasurement.ep_attribute,
"measured_value",
converter=lambda x: int(math.log10(x) * 10000 + 1) if x > 0 else int(0),
),
105: DPToAttributeMapping(
TuyaMmwRadarMotionAnalogInputCluster.ep_attribute,
"present_value",
converter=lambda x: PresenceMotionEnum(x),
endpoint_id=2,
),
106: DPToAttributeMapping(
TuyaMmwRadarMotionSensitivity.ep_attribute,
"present_value",
endpoint_id=6,
converter=lambda x: x if x < 10 else x / 10,
),
107: DPToAttributeMapping(
TuyaMmwRadarMaxRange.ep_attribute,
"present_value",
endpoint_id=3,
),
109: DPToAttributeMapping(
TuyaMmwRadarTargetDistanceAsPressureMeasurement.ep_attribute,
"measured_value",
),
110: DPToAttributeMapping(
TuyaMmwRadarFadingTime.ep_attribute,
"present_value",
endpoint_id=5,
),
111: DPToAttributeMapping(
TuyaMmwRadarPresenceSensitivity.ep_attribute,
"present_value",
endpoint_id=7,
converter=lambda x: x if x < 10 else x / 10,
),
112: DPToAttributeMapping(
TuyaOccupancySensing.ep_attribute,
"occupancy",
),
}
data_point_handlers = {
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",
109: "_dp_2_attr_update",
110: "_dp_2_attr_update",
111: "_dp_2_attr_update",
112: "_dp_2_attr_update",
}
class TuyaMmwRadarOccupancy(CustomDevice):
"""Millimeter wave occupancy sensor."""
def __init__(self, *args, **kwargs):
"""Init device."""
self.motion_bus = Bus()
super().__init__(*args, **kwargs)
signature = {
# endpoint=1, profile=260, device_type=81, device_version=1,
# input_clusters=[0, 4, 5, 61184], output_clusters=[25, 10]
MODELS_INFO: [
("_TZE204_ijxvkhd0", "TS0601"), # USB-C wall mounted 24GHz
#("_TZE204_e5m9c5hl", "TS0601"), # untested (and it uses a different config according to the zigbee2mqtt implementation)
],
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,
TuyaMmwRadarCluster,
TuyaIlluminanceMeasurement,
TuyaOccupancySensing,
TuyaMmwRadarTargetDistanceAsPressureMeasurement,
],
OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
},
2: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.OCCUPANCY_SENSOR,
INPUT_CLUSTERS: [
TuyaMmwRadarMotionAnalogInputCluster,
TuyaMmwRadarMotionSensing,
],
OUTPUT_CLUSTERS: [],
},
3: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
INPUT_CLUSTERS: [
TuyaMmwRadarMaxRange,
],
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: [
TuyaMmwRadarMotionSensitivity,
],
OUTPUT_CLUSTERS: [],
},
7: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
INPUT_CLUSTERS: [
TuyaMmwRadarPresenceSensitivity,
],
OUTPUT_CLUSTERS: [],
},
242: {
PROFILE_ID: 41440,
DEVICE_TYPE: 97,
INPUT_CLUSTERS: [],
OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
},
}
}
# another variant
class TuyaMmwRadarSensitivity(TuyaAttributesCluster, AnalogOutput):
"""AnalogOutput cluster for sensitivity."""
_CONSTANT_ATTRIBUTES = {
AnalogOutput.AttributeDefs.description.id: "sensitivity",
AnalogOutput.AttributeDefs.min_present_value.id: 1,
AnalogOutput.AttributeDefs.max_present_value.id: 9,
AnalogOutput.AttributeDefs.resolution.id: 1,
}
class TuyaMmwRadarMinRange(TuyaAttributesCluster, AnalogOutput):
"""AnalogOutput cluster for min range."""
_CONSTANT_ATTRIBUTES = {
AnalogOutput.AttributeDefs.description.id: "min_range",
AnalogOutput.AttributeDefs.min_present_value.id: 0,
AnalogOutput.AttributeDefs.max_present_value.id: 950,
AnalogOutput.AttributeDefs.resolution.id: 10,
AnalogOutput.AttributeDefs.engineering_units.id: 118, # 31: meters
}
class TuyaMmwRadarMaxRangeV2(TuyaAttributesCluster, AnalogOutput):
"""AnalogOutput cluster for max range."""
_CONSTANT_ATTRIBUTES = {
AnalogOutput.AttributeDefs.description.id: "max_range",
AnalogOutput.AttributeDefs.min_present_value.id: 10,
AnalogOutput.AttributeDefs.max_present_value.id: 950,
AnalogOutput.AttributeDefs.resolution.id: 10,
AnalogOutput.AttributeDefs.engineering_units.id: 118, # 31: meters
}
class TuyaMmwRadarDetectionDelay(TuyaAttributesCluster, AnalogOutput):
"""AnalogOutput cluster for detection delay."""
_CONSTANT_ATTRIBUTES = {
AnalogOutput.AttributeDefs.description.id: "detection_delay",
AnalogOutput.AttributeDefs.min_present_value.id: 000,
AnalogOutput.AttributeDefs.max_present_value.id: 20000,
AnalogOutput.AttributeDefs.resolution.id: 100,
AnalogOutput.AttributeDefs.engineering_units.id: 159, # 73: seconds
}
class TuyaMmwRadarFadingTimeV2(TuyaAttributesCluster, AnalogOutput):
"""AnalogOutput cluster for fading time."""
_CONSTANT_ATTRIBUTES = {
AnalogOutput.AttributeDefs.description.id: "fading_time",
AnalogOutput.AttributeDefs.min_present_value.id: 2000,
AnalogOutput.AttributeDefs.max_present_value.id: 200000,
AnalogOutput.AttributeDefs.resolution.id: 1000,
AnalogOutput.AttributeDefs.engineering_units.id: 159, # 73: seconds
}
class TuyaMmwRadarClusterBase(NoManufacturerCluster, TuyaMCUCluster):
"""Mmw radar cluster, base class."""
attributes = TuyaMCUCluster.attributes.copy()
attributes.update(
{
# Tuya 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),
}
)
class TuyaMmwRadarClusterV2(TuyaMmwRadarClusterBase):
"""Mmw radar cluster, variant 2 (5.8GHz)."""
dp_to_attribute: Dict[int, DPToAttributeMapping] = {
1: DPToAttributeMapping(
TuyaOccupancySensing.ep_attribute,
"occupancy",
),
2: DPToAttributeMapping(
TuyaMmwRadarSensitivity.ep_attribute,
"present_value",
endpoint_id=6,
),
3: DPToAttributeMapping(
TuyaMmwRadarMinRange.ep_attribute,
"present_value",
endpoint_id=2,
),
4: DPToAttributeMapping(
TuyaMmwRadarMaxRangeV2.ep_attribute,
"present_value",
endpoint_id=3,
),
9: DPToAttributeMapping(
TuyaMmwRadarTargetDistanceAsPressureMeasurement.ep_attribute,
"measured_value",
),
101: DPToAttributeMapping(
TuyaMmwRadarDetectionDelay.ep_attribute,
"present_value",
converter=lambda x: x * 100,
dp_converter=lambda x: x // 100,
endpoint_id=4,
),
102: DPToAttributeMapping(
TuyaMmwRadarFadingTimeV2.ep_attribute,
"present_value",
converter=lambda x: x * 100,
dp_converter=lambda x: x // 100,
endpoint_id=5,
),
104: DPToAttributeMapping(
TuyaIlluminanceMeasurement.ep_attribute,
"measured_value",
converter=lambda x: int(math.log10(x) * 10000 + 1) if x > 0 else int(0),
),
}
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",
101: "_dp_2_attr_update",
102: "_dp_2_attr_update",
104: "_dp_2_attr_update",
}
class TuyaMmwRadarOccupancyVariant2(CustomDevice):
"""Mini/Ceiling Human Breathe Sensor"""
def __init__(self, *args, **kwargs):
"""Init device."""
self.motion_bus = Bus()
super().__init__(*args, **kwargs)
signature = {
# endpoint=1, profile=260, device_type=81, device_version=1,
# input_clusters=[4, 5, 61184, 0], output_clusters=[25, 10]
MODELS_INFO: [
("_TZE204_qasjif9e", "TS0601"), # USB-C wall mounted 5.8GHz
("_TZE204_ztqnh5cg", "TS0601"), # untested with ZHA but uses same quirk as _TZE204_qasjif9e for zigbee2mqtt
],
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, device_version=0, input_clusters=[], output_clusters=[33]
# input_clusters=[]
# output_clusters=[33]
PROFILE_ID: zgp.PROFILE_ID,
DEVICE_TYPE: zgp.DeviceType.PROXY_BASIC,
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,
TuyaMmwRadarClusterV2,
TuyaIlluminanceMeasurement,
TuyaOccupancySensing,
TuyaMmwRadarTargetDistanceAsPressureMeasurement,
],
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: [
TuyaMmwRadarMaxRangeV2,
],
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: [
TuyaMmwRadarFadingTimeV2,
],
OUTPUT_CLUSTERS: [],
},
6: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
INPUT_CLUSTERS: [
TuyaMmwRadarSensitivity,
],
OUTPUT_CLUSTERS: [],
},
242: {
PROFILE_ID: zgp.PROFILE_ID,
DEVICE_TYPE: zgp.DeviceType.PROXY_BASIC,
INPUT_CLUSTERS: [],
OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
},
}
}
|
hi, ah thats great, thank you. I've just tested it and it actually works with the pressure/distance part which i just couldn't get working from hacking around with other quirks. it does seem to be all over the place though but that won't be anything to do with your code and is likely to do with the radar sensor itself. so after all of this its probably going to be a bit tricky to make any automations with the numbers it gives lol thanks again @logan893 , you're a hero |
@logan893 sorry to be a PITA, i've just got another (ceiling, 24ghz model) which has a new model number I can't find any references to. I've tried to use your above 24ghz quirk , added the model number, it picks it up, but it says illuminance and pressure are 'unknown' and motion/presence are always 'Clear' and don't seem to pick any movement up. any ideas? or do you need to have a physical device to be able to advise? stupidly I forgot to post the model - its _TZE204_bmdsp6bs https://www.aliexpress.com/item/1005006529472748.html thanks edit: i've just done a ZHA Toolbox - scan device, once whilst room lit and empty and again when i'm sitting in the room and darker... hoping that the two outputs would list different values for some of the attributes, but alas the only thing differing between the two documents is the timestamps :-( |
ok so i managed to get the occupancy being reported by literally using the quirk from |
ugh, ok so i REALLY need to be able to configure the settings on this since it seems to "clear" after no movement within like 10 seconds. it really shouldnt do that should it? i thought the whole point in radar was that it detected presence despite no movement!! :-( |
@thefunkygibbon I believe you'll need to either be very good or lucky at guessing, or have a Tuya gateway, to figure out how these new devices work. I don't have a gateway myself, so most of the quirks I posted here are based on information others have already discovered, and all things considered just a tiny bit of guesswork and trial and error. Zigbee2MQTT has a tutorial for how to extract the device data points from logs using a Tuya gateway and the Tuya app. https://www.zigbee2mqtt.io/advanced/support-new-devices/03_find_tuya_data_points.html |
don't suppose anyone has managed to get a decent quirk working for these devices have they? I really need to get these working else i'll have to find some other ceiling mounted, water resistant radar devices (which aren't tuya) ... and to be honest, there isn't a lot out there |
Problem description
Pairing with Home Assistant but no sensors or controls available. Advertised as Presence sensor for use with Tuya zigbee hub/gateway.
Model: ZY-M100-1 (Side wall version); there is also the ZY-M100-2 Ceiling version, which I do not have myself.
Shows up in ZHA as TS0601 by _TZE204_ijxvkhd0
This is a mains powered 24 GHz presence sensor, zigbee and side wall edition.
https://www.aliexpress.com/item/1005006128737558.html
Solution description
Presence Sensor functionality
Screenshots/Video
Screenshots/Video
[Paste/upload your media here]
Device signature
Device signature
[Paste the device signature here]
Diagnostic information
Diagnostic information
[Paste the diagnostic information here]
Logs
Logs
Custom quirk
Custom quirk
Additional information
No response
The text was updated successfully, but these errors were encountered: