zigpy / zha-device-handlers

ZHA device handlers bridge the functionality gap created when manufacturers deviate from the ZCL specification, handling deviations and exceptions by parsing custom messages to and from Zigbee devices.
Apache License 2.0
724 stars 671 forks source link

[Device Support Request] Tuya Zigbee Human Presence Sensor (_TZE200_9qayzqa8 TS0601) #1618

Closed wryandginger closed 1 year ago

wryandginger commented 2 years ago

Is your feature request related to a problem? Please describe.

I just bought a Tuya Zigbee Human Presence Sensor (Model is listed as LQ-CG01-RDR in the manual) It pairs, but shows no entities

Describe the solution you'd like I'd like to see a presence entity

Device signature ```yaml { "node_descriptor": "NodeDescriptor(logical_type=, complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=, mac_capability_flags=, manufacturer_code=4417, maximum_buffer_size=66, maximum_incoming_transfer_size=66, server_mask=10752, maximum_outgoing_transfer_size=66, descriptor_capability_field=, *allocate_address=True, *is_alternate_pan_coordinator=False, *is_coordinator=False, *is_end_device=False, *is_full_function_device=True, *is_mains_powered=True, *is_receiver_on_when_idle=True, *is_router=True, *is_security_capable=False)", "endpoints": { "1": { "profile_id": 260, "device_type": "0x0051", "in_clusters": [ "0x0000", "0x0004", "0x0005", "0xef00" ], "out_clusters": [ "0x000a", "0x0019" ] }, "242": { "profile_id": 41440, "device_type": "0x0061", "in_clusters": [], "out_clusters": [ "0x0021" ] } }, "manufacturer": "_TZE200_9qayzqa8", "model": "TS0601", "class": "zigpy.device.Device" } ```
Diagnostic information ```yaml { "home_assistant": { "installation_type": "Home Assistant OS", "version": "2022.6.5", "dev": false, "hassio": true, "virtualenv": false, "python_version": "3.9.12", "docker": true, "arch": "x86_64", "timezone": "America/Los_Angeles", "os_name": "Linux", "os_version": "5.15.41", "supervisor": "2022.05.3", "host_os": "Home Assistant OS 8.1", "docker_version": "20.10.14", "chassis": "embedded", "run_as_root": true }, "custom_components": { "watchman": { "version": "0.5.1", "requirements": [ "prettytable==3.0.0" ] }, "smartthinq_sensors": { "version": "0.23.0", "requirements": [ "pycountry>=20.7.3", "xmltodict>=0.12.0", "chardet>=4.0.0" ] }, "dyson_local": { "version": "0.16.4-4", "requirements": [ "libdyson==0.8.11" ] }, "frigate": { "version": "2.3", "requirements": [] }, "zha_toolkit": { "version": "v0.8.11", "requirements": [] }, "dyson_cloud": { "version": "0.15.0", "requirements": [ "libdyson==0.8.7" ] }, "alexa_media": { "version": "4.0.2", "requirements": [ "alexapy==1.26.0", "packaging>=20.3", "wrapt>=1.12.1" ] }, "hacs": { "version": "1.25.5", "requirements": [ "aiogithubapi>=22.2.4" ] }, "govee": { "version": "0.2.2", "requirements": [ "govee-api-laggat==0.2.2", "dacite==1.6.0" ] } }, "integration_manifest": { "domain": "zha", "name": "Zigbee Home Automation", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/zha", "requirements": [ "bellows==0.30.0", "pyserial==3.5", "pyserial-asyncio==0.6", "zha-quirks==0.0.75", "zigpy-deconz==0.16.0", "zigpy==0.45.1", "zigpy-xbee==0.14.0", "zigpy-zigate==0.7.4", "zigpy-znp==0.7.0" ], "usb": [ { "vid": "10C4", "pid": "EA60", "description": "*2652*", "known_devices": [ "slae.sh cc2652rb stick" ] }, { "vid": "10C4", "pid": "EA60", "description": "*sonoff*plus*", "known_devices": [ "sonoff zigbee dongle plus" ] }, { "vid": "10C4", "pid": "EA60", "description": "*tubeszb*", "known_devices": [ "TubesZB Coordinator" ] }, { "vid": "1A86", "pid": "7523", "description": "*tubeszb*", "known_devices": [ "TubesZB Coordinator" ] }, { "vid": "1A86", "pid": "7523", "description": "*zigstar*", "known_devices": [ "ZigStar Coordinators" ] }, { "vid": "1CF1", "pid": "0030", "description": "*conbee*", "known_devices": [ "Conbee II" ] }, { "vid": "10C4", "pid": "8A2A", "description": "*zigbee*", "known_devices": [ "Nortek HUSBZB-1" ] }, { "vid": "0403", "pid": "6015", "description": "*zigate*", "known_devices": [ "ZiGate+" ] }, { "vid": "10C4", "pid": "EA60", "description": "*zigate*", "known_devices": [ "ZiGate" ] }, { "vid": "10C4", "pid": "8B34", "description": "*bv 2010/10*", "known_devices": [ "Bitron Video AV2010/10" ] } ], "codeowners": [ "@dmulcahey", "@adminiuga" ], "zeroconf": [ { "type": "_esphomelib._tcp.local.", "name": "tube*" }, { "type": "_zigate-zigbee-gateway._tcp.local.", "name": "*zigate*" } ], "after_dependencies": [ "usb", "zeroconf" ], "iot_class": "local_polling", "loggers": [ "aiosqlite", "bellows", "crccheck", "pure_pcapy3", "zhaquirks", "zigpy", "zigpy_deconz", "zigpy_xbee", "zigpy_zigate", "zigpy_znp" ], "is_built_in": true }, "data": { "ieee": "**REDACTED**", "nwk": 53347, "manufacturer": "_TZE200_9qayzqa8", "model": "TS0601", "name": "_TZE200_9qayzqa8 TS0601", "quirk_applied": false, "quirk_class": "zigpy.device.Device", "manufacturer_code": 4417, "power_source": "Mains", "lqi": 255, "rssi": -67, "last_seen": "2022-06-11T16:27:24", "available": true, "device_type": "Router", "signature": { "node_descriptor": "NodeDescriptor(logical_type=, complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=, mac_capability_flags=, manufacturer_code=4417, maximum_buffer_size=66, maximum_incoming_transfer_size=66, server_mask=10752, maximum_outgoing_transfer_size=66, descriptor_capability_field=, *allocate_address=True, *is_alternate_pan_coordinator=False, *is_coordinator=False, *is_end_device=False, *is_full_function_device=True, *is_mains_powered=True, *is_receiver_on_when_idle=True, *is_router=True, *is_security_capable=False)", "endpoints": { "1": { "profile_id": 260, "device_type": "0x0051", "in_clusters": [ "0x0000", "0x0004", "0x0005", "0xef00" ], "out_clusters": [ "0x000a", "0x0019" ] }, "242": { "profile_id": 41440, "device_type": "0x0061", "in_clusters": [], "out_clusters": [ "0x0021" ] } } }, "entities": [], "neighbors": [], "endpoint_names": [ { "name": "SMART_PLUG" }, { "name": "unknown 97 device_type of 0xa1e0 profile id" } ], "user_given_name": null, "device_reg_id": "2c6eff44ecf555ac6baa0f87e78aa827", "area_id": null } } ```
Additional logs ``` ```

Additional context https://snipboard.io/OnSaFh.jpg https://snipboard.io/40lnZe.jpg https://snipboard.io/zocPLk.jpg

Update: I tried to roll my own quirk using the info here: https://github.com/zigpy/zha-device-handlers/issues/1590 I appended the manufacturer info (_TZE200_9qayzqa8 TS0601) to the TS0601_motion.py file and put it in as a custom quirk. It does expose the occupancy entity, but never shows motion.

javicalle commented 2 years ago

Hi, any link to the manufacturer/vendor site? This can give us some clues about the device capabilities.

Enable debug logs and attach the device relevant info of the device, identifying the events (movement, etc) with logs. We need to match the device reported info with the device capabilities.

Here is info about enable debug logs:

wryandginger commented 2 years ago

I think it's made by Aubess, but there's no branding or company listed on the box or in the manual other than Tuya. AliExpress listings say it is a PIR sensor, but this is clearly mmWave (see my images, above)

Here are the AliExpress listings that are selling the device:

Based on my tinkering so far, it looks like the occupancy is happening on dp_119 or dp_141, which aren't being assigned in the current ts0601_motion.py. When I tried to add them in a custom quirk (under "class MmwRadarMotionGPP(CustomDevice)") it seemed to show presence activity, but then gets stuck on "presence detected."

I enabled logs and I'll re-pair them and report back later.

wryandginger commented 2 years ago

Okay, I enabled logs, but I'm not sure if this is what you need. There's a lot of crap in my logs to sort through.

Pairing, I think: ```yaml 2022-06-15 10:19:24 DEBUG (MainThread) [zigpy_deconz.uart] Frame received: 0x174c002a0023002202fdff0002e71900000013000c0000e7194adbcb2ac738c1a48e00afff3f2f0020d0 2022-06-15 10:19:24 DEBUG (MainThread) [zigpy_deconz.api] APS data indication response: [35, , , 0, , 0, 0, 19, b'\x00\xe7\x19J\xdb\xcb*\xc78\xc1\xa4\x8e', 0, 175, 255, 63, 47, 0, 32, -48] 2022-06-15 10:19:24 INFO (MainThread) [zigpy_deconz.zigbee.application] New device joined: 0x19e7, a4:c1:38:c7:2a:cb:db:4a 2022-06-15 10:19:24 INFO (MainThread) [zigpy.application] New device 0x19e7 (a4:c1:38:c7:2a:cb:db:4a) joined the network 2022-06-15 10:19:24 DEBUG (MainThread) [zigpy.device] [0x19e7] Scheduling initialization 2022-06-15 10:19:24 DEBUG (MainThread) [zigpy.application] Received frame on uninitialized device from ep 0 to ep 0, cluster 19: b'\x00\xe7\x19J\xdb\xcb*\xc78\xc1\xa4\x8e' 2022-06-15 10:19:24 DEBUG (MainThread) [zigpy.zdo] [0x19e7:zdo] ZDO request ZDOCmd.Device_annce: [0x19E7, a4:c1:38:c7:2a:cb:db:4a, 142] 2022-06-15 10:19:24 DEBUG (MainThread) [zigpy.util] Tries remaining: 3 2022-06-15 10:19:24 INFO (MainThread) [zigpy.device] [0x19e7] Requesting 'Node Descriptor' 2022-06-15 10:19:24 DEBUG (MainThread) [zigpy.util] Tries remaining: 2 2022-06-15 10:19:24 DEBUG (MainThread) [zigpy.device] [0x19e7] Extending timeout for 0x5f request 2022-06-15 10:19:24 DEBUG (MainThread) [zigpy_deconz.zigbee.application] Sending Zigbee request with tsn 95 under 96 request id, data: b'5fe719' 2022-06-15 10:19:24 DEBUG (MainThread) [zigpy_deconz.api] 'aps_data_indication' response from , ep: 0, profile: 0x0000, cluster_id: 0x0013, data: b'00e7194adbcb2ac738c1a48e' 2022-06-15 10:19:24 DEBUG (MainThread) [zigpy_deconz.api] Command Command.aps_data_request (18, 96, , , 0, , 0, b'_\xe7\x19', , 0) 2022-06-15 10:19:24 DEBUG (MainThread) [zigpy_deconz.uart] Send: 0x124d0019001200600002e71900000002000003005fe7190200 2022-06-15 10:19:24 DEBUG (MainThread) [zigpy_deconz.uart] Frame received: 0x124d00090002002260 2022-06-15 10:19:24 DEBUG (MainThread) [zigpy_deconz.api] APS data request response: [2, , 96] 2022-06-15 10:19:24 DEBUG (MainThread) [zigpy_deconz.uart] Frame received: 0x0e4e000700aa00 2022-06-15 10:19:24 DEBUG (MainThread) [zigpy_deconz.api] Device state changed response: [, 0] 2022-06-15 10:19:24 DEBUG (MainThread) [zigpy_deconz.api] Command Command.aps_data_indication (1, 1) 2022-06-15 10:19:24 DEBUG (MainThread) [zigpy_deconz.uart] Send: 0x174e000800010001 2022-06-15 10:19:24 DEBUG (MainThread) [zigpy_deconz.uart] Frame received: 0x174e002b002400220200000102d03c01040100ef0d00097c010002150200040000000c00afff3f2f0020d5 ```
Motion Event, I think: ```yaml 2022-06-15 10:20:52 DEBUG (MainThread) [zigpy_deconz.api] Device state changed response: [, 0] 2022-06-15 10:20:52 DEBUG (MainThread) [zigpy_deconz.api] Command Command.aps_data_indication (1, 1) 2022-06-15 10:20:52 DEBUG (MainThread) [zigpy_deconz.api] APS data indication response: [33, , , 1, , 1, 260, 61184, b'\ts\x02\x00$w\x04\x00\x01\x01', 0, 175, 255, 134, 26, 18, 8, -46] 2022-06-15 10:20:52 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] Received ZCL frame: b'\ts\x02\x00$w\x04\x00\x01\x01' 2022-06-15 10:20:52 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] Decoded ZCL frame header: ZCLHeader(frame_control=FrameControl(frame_type=, is_manufacturer_specific=0, is_reply=1, disable_default_response=0, reserved=0, *is_cluster=True, *is_general=False), tsn=115, command_id=2, *is_reply=True) 2022-06-15 10:20:52 WARNING (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] Unknown cluster command 2 b'\x00$w\x04\x00\x01\x01' 2022-06-15 10:20:52 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] Received command 0x02 (TSN 115): b'\x00$w\x04\x00\x01\x01' 2022-06-15 10:20:52 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] No explicit handler for cluster command 0x02: b'\x00$w\x04\x00\x01\x01' 2022-06-15 10:20:52 DEBUG (MainThread) [zigpy_deconz.api] 'aps_data_indication' response from , ep: 1, profile: 0x0104, cluster_id: 0xef00, data: b'09730200247704000101' ```
another Motion Event, I think: ```yaml 2022-06-15 10:42:37 DEBUG (MainThread) [zigpy_deconz.api] Command Command.aps_data_indication (1, 1) 2022-06-15 10:42:37 DEBUG (MainThread) [zigpy_deconz.api] APS data indication response: [33, , , 1, , 1, 260, 61184, b'\t{\x02\x00\x12\x8d\x04\x00\x01\x02', 0, 175, 255, 184, 49, 18, 8, -40] 2022-06-15 10:42:37 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] Received ZCL frame: b'\t{\x02\x00\x12\x8d\x04\x00\x01\x02' 2022-06-15 10:42:37 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] Decoded ZCL frame header: ZCLHeader(frame_control=FrameControl(frame_type=, is_manufacturer_specific=0, is_reply=1, disable_default_response=0, reserved=0, *is_cluster=True, *is_general=False), tsn=123, command_id=2, *is_reply=True) 2022-06-15 10:42:37 WARNING (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] Unknown cluster command 2 b'\x00\x12\x8d\x04\x00\x01\x02' 2022-06-15 10:42:37 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] Received command 0x02 (TSN 123): b'\x00\x12\x8d\x04\x00\x01\x02' 2022-06-15 10:42:37 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] No explicit handler for cluster command 0x02: b'\x00\x12\x8d\x04\x00\x01\x02' 2022-06-15 10:42:37 DEBUG (MainThread) [zigpy_deconz.api] 'aps_data_indication' response from , ep: 1, profile: 0x0104, cluster_id: 0xef00, data: b'097b0200128d04000102' 2022-06-15 10:42:37 DEBUG (MainThread) [zigpy_deconz.api] Command Command.aps_data_confirm (0,) 2022-06-15 10:42:37 DEBUG (MainThread) [zigpy_deconz.api] APS data confirm response for request with id 157: 00
javicalle commented 2 years ago

This logs are dificult to me to analyze, so if you don't matter, we can come back to the quirk and work from it. The logs from the quirk will give to us more information (or in the format usefull to us).

My proposal quirk is this:

ts0601_radar.py ```python """Tuya mmWave sensor quirk.""" 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, 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 ( TuyaLocalCluster, TuyaManufCluster, TuyaNewManufCluster, ) from zhaquirks.tuya.mcu import DPToAttributeMapping, TuyaDPType, TuyaMCUCluster 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 MmwRadarManufCluster(TuyaMCUCluster): """Neo manufacturer cluster.""" attributes = TuyaMCUCluster.attributes.copy() attributes.update( { # ramdom attribute IDs 0xEF01: ("dp_1", t.uint32_t, True), 0xEF02: ("dp_2", t.uint32_t, True), 0xEF03: ("dp_3", t.uint32_t, True), 0xEF04: ("dp_4", t.uint32_t, True), 0xEF06: ("dp_6", t.enum8, True), 0xEF65: ("dp_101", t.uint32_t, True), 0xEF66: ("dp_102", t.uint32_t, True), 0xEF67: ("dp_103", t.CharacterString, True), 0xEF69: ("dp_105", t.enum8, True), 0xEF6A: ("dp_106", t.enum8, True), 0xEF6B: ("dp_107", t.enum8, True), 0xEF6C: ("dp_108", t.uint32_t, True), 0xEF8D: ("dp_141", t.enum8, True), } ) dp_to_attribute: Dict[int, DPToAttributeMapping] = { 1: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "dp_1", dp_type=TuyaDPType.VALUE, ), 2: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "dp_2", dp_type=TuyaDPType.VALUE, ), 3: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "dp_3", dp_type=TuyaDPType.VALUE, ), 4: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "dp_4", dp_type=TuyaDPType.VALUE, ), 6: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "dp_6", dp_type=TuyaDPType.ENUM, ), 9: DPToAttributeMapping( TuyaAnalogInput.ep_attribute, "present_value", converter=lambda x: x / 100, dp_type=TuyaDPType.VALUE, ), 101: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "dp_101", dp_type=TuyaDPType.VALUE, ), 102: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "dp_102", dp_type=TuyaDPType.VALUE, ), 103: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "dp_103", dp_type=TuyaDPType.STRING, ), 104: DPToAttributeMapping( TuyaIlluminanceMeasurement.ep_attribute, "measured_value", converter=lambda x: 10000 * math.log10(x) + 1, dp_type=TuyaDPType.VALUE, ), 105: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "dp_105", dp_type=TuyaDPType.ENUM, ), 106: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "dp_106", dp_type=TuyaDPType.ENUM, ), 107: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "dp_107", dp_type=TuyaDPType.ENUM, ), 108: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "dp_108", dp_type=TuyaDPType.VALUE, ), 119: DPToAttributeMapping( TuyaOccupancySensing.ep_attribute, "occupancy", dp_type=TuyaDPType.ENUM, ), 141: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "dp_141", dp_type=TuyaDPType.ENUM, ), } 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", 105: "_dp_2_attr_update", 106: "_dp_2_attr_update", 107: "_dp_2_attr_update", 108: "_dp_2_attr_update", 119: "_dp_2_attr_update", 141: "_dp_2_attr_update", } class MmwRadarMotionGPP(CustomDevice): """Millimeter wave occupancy sensor.""" signature = { # endpoint=1, profile=260, device_type=81, device_version=1, # input_clusters=[4, 5, 61184, 0], output_clusters=[25, 10]) MODELS_INFO: [ ("_TZE200_9qayzqa8", "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: { #

There's a lot of crap in my logs to sort through.

Yes, it is very verbose. But if you can get the logs from the same events, it will give us the DPs and values that device is using. Then we can try to map DPs to functionallity.

wryandginger commented 2 years ago

Thanks for your help, @javicalle. I created a TuyaMMWSensor.py file using your proposed quirk and put it in /config/custom_zha_quirks and rebooted.

Here's what gets flagged in the UI:
[0x19E7:1:0xef00] No 'handle_set_data_response' tuya handler found for set_data_response(data=TuyaCommand(status=0, tsn=164, dp=141, data=TuyaData(dp_type=<TuyaDPType.ENUM: 4>, function=0, raw=b'\x02', *payload=<enum8.undefined_0x02: 2>)))
[0x19E7:1:0xef00] No 'handle_set_data_response' tuya handler found for set_data_response(data=TuyaCommand(status=0, tsn=165, dp=119, data=TuyaData(dp_type=<TuyaDPType.ENUM: 4>, function=0, raw=b'\x01', *payload=<enum8.undefined_0x01: 1>)))
[0x19E7:1:0xef00] No 'handle_set_data_response' tuya handler found for set_data_response(data=TuyaCommand(status=0, tsn=166, dp=141, data=TuyaData(dp_type=<TuyaDPType.ENUM: 4>, function=0, raw=b'\x02', *payload=<enum8.undefined_0x02: 2>)))
[0x19E7:1:0xef00] No 'handle_set_data_response' tuya handler found for set_data_response(data=TuyaCommand(status=0, tsn=167, dp=119, data=TuyaData(dp_type=<TuyaDPType.ENUM: 4>, function=0, raw=b'\x01', *payload=<enum8.undefined_0x01: 1>)))
[0x19E7:1:0xef00] No 'handle_set_data_response' tuya handler found for set_data_response(data=TuyaCommand(status=0, tsn=168, dp=141, data=TuyaData(dp_type=<TuyaDPType.ENUM: 4>, function=0, raw=b'\x02', *payload=<enum8.undefined_0x02: 2>)))

And if I look at the home-assistant.log:

Here's a motion event:
2022-06-15 13:25:30 DEBUG (MainThread) [zigpy_deconz.api] Command Command.aps_data_indication (1, 1)
2022-06-15 13:25:30 DEBUG (MainThread) [zigpy_deconz.api] APS data indication response: [33, <DeviceState.APSDE_DATA_REQUEST_SLOTS_AVAILABLE|2: 34>, <DeconzAddress address_mode=ADDRESS_MODE.NWK address=0x0000>, 1, <DeconzAddress address_mode=ADDRESS_MODE.NWK address=0x19e7>, 1, 260, 61184, b'\tF\x02\x00%w\x04\x00\x01\x00', 0, 175, 255, 22, 149, 18, 8, -40]
2022-06-15 13:25:30 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] Received ZCL frame: b'\tF\x02\x00%w\x04\x00\x01\x00'
2022-06-15 13:25:30 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] Decoded ZCL frame header: ZCLHeader(frame_control=FrameControl(frame_type=<FrameType.CLUSTER_COMMAND: 1>, is_manufacturer_specific=0, is_reply=1, disable_default_response=0, reserved=0, *is_cluster=True, *is_general=False), tsn=70, command_id=2, *is_reply=True)
2022-06-15 13:25:30 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] Decoded ZCL frame: MmwRadarManufCluster:set_data_response(data=TuyaCommand(status=0, tsn=37, dp=119, data=TuyaData(dp_type=<TuyaDPType.ENUM: 4>, function=0, raw=b'\x00', *payload=<enum8.undefined_0x00: 0>)))
2022-06-15 13:25:30 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] Received command 0x02 (TSN 70): set_data_response(data=TuyaCommand(status=0, tsn=37, dp=119, data=TuyaData(dp_type=<TuyaDPType.ENUM: 4>, function=0, raw=b'\x00', *payload=<enum8.undefined_0x00: 0>)))
2022-06-15 13:25:30 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] No datapoint handler for TuyaCommand(status=0, tsn=37, dp=119, data=TuyaData(dp_type=<TuyaDPType.ENUM: 4>, function=0, raw=b'\x00', *payload=<enum8.undefined_0x00: 0>))
2022-06-15 13:25:30 WARNING (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] No 'handle_set_data_response' tuya handler found for set_data_response(data=TuyaCommand(status=0, tsn=37, dp=119, data=TuyaData(dp_type=<TuyaDPType.ENUM: 4>, function=0, raw=b'\x00', *payload=<enum8.undefined_0x00: 0>)))
2022-06-15 13:25:30 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] Sending reply header: ZCLHeader(frame_control=FrameControl(frame_type=<FrameType.GLOBAL_COMMAND: 0>, is_manufacturer_specific=False, is_reply=1, disable_default_response=1, reserved=0, *is_cluster=False, *is_general=True), tsn=70, command_id=<GeneralCommand.Default_Response: 11>, *is_reply=True)
2022-06-15 13:25:30 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] Sending reply: Default_Response(command_id=2, status=<Status.UNSUP_CLUSTER_COMMAND: 129>)
2022-06-15 13:25:30 DEBUG (MainThread) [zigpy_deconz.zigbee.application] Sending Zigbee request with tsn 70 under 96 request id, data: b'18460b0281'
2022-06-15 13:25:30 DEBUG (MainThread) [zigpy_deconz.zigbee.application] Max concurrency (8) reached, delaying requests (158 enqueued)
2022-06-15 13:25:30 DEBUG (MainThread) [zigpy_deconz.api] 'aps_data_indication' response from <DeconzAddress address_mode=ADDRESS_MODE.NWK address=0x19e7>, ep: 1, profile: 0x0104, cluster_id: 0xef00, data: b'09460200257704000100'
2022-06-15 13:25:30 DEBUG (MainThread) [zigpy_deconz.api] Device state changed response: [<DeviceState.128|APSDE_DATA_REQUEST_SLOTS_AVAILABLE|APSDE_DATA_INDICATION|APSDE_DATA_CONFIRM|2: 174>, 0]
2022-06-15 13:25:30 DEBUG (MainThread) [zigpy_deconz.api] Command Command.aps_data_indication (1, 1)
Here's another: ```yaml 2022-06-15 13:36:10 DEBUG (MainThread) [zigpy_deconz.api] Device state changed response: [, 0] 2022-06-15 13:36:10 DEBUG (MainThread) [zigpy_deconz.api] Command Command.aps_data_indication (1, 1) 2022-06-15 13:36:10 DEBUG (MainThread) [zigpy_deconz.api] APS data indication response: [33, , , 1, , 1, 260, 61184, b'\t}\x02\x007w\x04\x00\x01\x01', 0, 175, 255, 120, 250, 18, 8, -40] 2022-06-15 13:36:10 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] Received ZCL frame: b'\t}\x02\x007w\x04\x00\x01\x01' 2022-06-15 13:36:10 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] Decoded ZCL frame header: ZCLHeader(frame_control=FrameControl(frame_type=, is_manufacturer_specific=0, is_reply=1, disable_default_response=0, reserved=0, *is_cluster=True, *is_general=False), tsn=125, command_id=2, *is_reply=True) 2022-06-15 13:36:10 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] Decoded ZCL frame: MmwRadarManufCluster:set_data_response(data=TuyaCommand(status=0, tsn=55, dp=119, data=TuyaData(dp_type=, function=0, raw=b'\x01', *payload=))) 2022-06-15 13:36:10 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] Received command 0x02 (TSN 125): set_data_response(data=TuyaCommand(status=0, tsn=55, dp=119, data=TuyaData(dp_type=, function=0, raw=b'\x01', *payload=))) 2022-06-15 13:36:10 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] No datapoint handler for TuyaCommand(status=0, tsn=55, dp=119, data=TuyaData(dp_type=, function=0, raw=b'\x01', *payload=)) 2022-06-15 13:36:10 WARNING (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] No 'handle_set_data_response' tuya handler found for set_data_response(data=TuyaCommand(status=0, tsn=55, dp=119, data=TuyaData(dp_type=, function=0, raw=b'\x01', *payload=))) 2022-06-15 13:36:10 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] Sending reply header: ZCLHeader(frame_control=FrameControl(frame_type=, is_manufacturer_specific=False, is_reply=1, disable_default_response=1, reserved=0, *is_cluster=False, *is_general=True), tsn=125, command_id=, *is_reply=True) 2022-06-15 13:36:10 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] Sending reply: Default_Response(command_id=2, status=) 2022-06-15 13:36:10 DEBUG (MainThread) [zigpy_deconz.zigbee.application] Sending Zigbee request with tsn 125 under 246 request id, data: b'187d0b0281' 2022-06-15 13:36:10 DEBUG (MainThread) [zigpy_deconz.api] 'aps_data_indication' response from , ep: 1, profile: 0x0104, cluster_id: 0xef00, data: b'097d0200377704000101' 2022-06-15 13:36:10 DEBUG (MainThread) [zigpy_deconz.api] Command Command.aps_data_request (20, 246, , , 260, 61184, 1, b'\x18}\x0b\x02\x81', , 0) 2022-06-15 13:36:10 DEBUG (MainThread) [zigpy_deconz.api] APS data request response: [2, , 246] 2022-06-15 13:36:10 DEBUG (MainThread) [zigpy_deconz.api] Device state changed response: [, 0] 2022-06-15 13:36:10 DEBUG (MainThread) [zigpy_deconz.api] Command Command.aps_data_confirm (0,) 2022-06-15 13:36:10 DEBUG (MainThread) [zigpy_deconz.api] APS data confirm response for request with id 246: 00 2022-06-15 13:36:10 DEBUG (MainThread) [zigpy_deconz.api] Request id: 0xf6 'aps_data_confirm' for , status: 0x00 2022-06-15 13:36:10 DEBUG (MainThread) [zigpy_deconz.api] Device state changed response: [, 0] 2022-06-15 13:36:10 DEBUG (MainThread) [zigpy_deconz.api] Command Command.aps_data_indication (1, 1) 2022-06-15 13:36:10 DEBUG (MainThread) [zigpy_deconz.api] APS data indication response: [33, , , 1, , 1, 260, 61184, b'\t~\x02\x008\x8d\x04\x00\x01\x02', 0, 175, 255, 120, 250, 18, 8, -40] 2022-06-15 13:36:10 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] Received ZCL frame: b'\t~\x02\x008\x8d\x04\x00\x01\x02' 2022-06-15 13:36:10 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] Decoded ZCL frame header: ZCLHeader(frame_control=FrameControl(frame_type=, is_manufacturer_specific=0, is_reply=1, disable_default_response=0, reserved=0, *is_cluster=True, *is_general=False), tsn=126, command_id=2, *is_reply=True) 2022-06-15 13:36:10 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] Decoded ZCL frame: MmwRadarManufCluster:set_data_response(data=TuyaCommand(status=0, tsn=56, dp=141, data=TuyaData(dp_type=, function=0, raw=b'\x02', *payload=))) 2022-06-15 13:36:10 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] Received command 0x02 (TSN 126): set_data_response(data=TuyaCommand(status=0, tsn=56, dp=141, data=TuyaData(dp_type=, function=0, raw=b'\x02', *payload=))) 2022-06-15 13:36:10 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] No datapoint handler for TuyaCommand(status=0, tsn=56, dp=141, data=TuyaData(dp_type=, function=0, raw=b'\x02', *payload=)) 2022-06-15 13:36:10 WARNING (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] No 'handle_set_data_response' tuya handler found for set_data_response(data=TuyaCommand(status=0, tsn=56, dp=141, data=TuyaData(dp_type=, function=0, raw=b'\x02', *payload=))) 2022-06-15 13:36:10 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] Sending reply header: ZCLHeader(frame_control=FrameControl(frame_type=, is_manufacturer_specific=False, is_reply=1, disable_default_response=1, reserved=0, *is_cluster=False, *is_general=True), tsn=126, command_id=, *is_reply=True) 2022-06-15 13:36:10 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] Sending reply: Default_Response(command_id=2, status=) 2022-06-15 13:36:10 DEBUG (MainThread) [zigpy_deconz.zigbee.application] Sending Zigbee request with tsn 126 under 247 request id, data: b'187e0b0281' 2022-06-15 13:36:10 DEBUG (MainThread) [zigpy_deconz.api] 'aps_data_indication' response from , ep: 1, profile: 0x0104, cluster_id: 0xef00, data: b'097e0200388d04000102' 2022-06-15 13:36:10 DEBUG (MainThread) [zigpy_deconz.api] Command Command.aps_data_request (20, 247, , , 260, 61184, 1, b'\x18~\x0b\x02\x81', , 0) 2022-06-15 13:36:10 DEBUG (MainThread) [zigpy_deconz.api] APS data request response: [2, , 247] 2022-06-15 13:36:10 DEBUG (MainThread) [zigpy_deconz.api] Device state changed response: [, 0] 2022-06-15 13:36:10 DEBUG (MainThread) [zigpy_deconz.api] Command Command.aps_data_confirm (0,) 2022-06-15 13:36:10 DEBUG (MainThread) [zigpy_deconz.api] APS data confirm response for request with id 247: 00 2022-06-15 13:36:10 DEBUG (MainThread) [zigpy_deconz.api] Request id: 0xf7 'aps_data_confirm' for , status: 0x00 2022-06-15 13:36:10 DEBUG (MainThread) [zigpy_deconz.api] Device state changed response: [, 0] 2022-06-15 13:36:10 DEBUG (MainThread) [zigpy_deconz.api] Command Command.aps_data_indication (1, 1) ```
And a third: ```yaml 2022-06-15 13:40:09 DEBUG (MainThread) [zigpy_deconz.api] Device state changed response: [, 0] 2022-06-15 13:40:09 DEBUG (MainThread) [zigpy_deconz.api] Command Command.aps_data_indication (1, 1) 2022-06-15 13:40:09 DEBUG (MainThread) [zigpy_deconz.api] APS data indication response: [33, , , 1, , 1, 260, 61184, b'\t\xb6\x02\x00ow\x04\x00\x01\x01', 0, 175, 255, 202, 254, 18, 8, -40] 2022-06-15 13:40:09 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] Received ZCL frame: b'\t\xb6\x02\x00ow\x04\x00\x01\x01' 2022-06-15 13:40:09 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] Decoded ZCL frame header: ZCLHeader(frame_control=FrameControl(frame_type=, is_manufacturer_specific=0, is_reply=1, disable_default_response=0, reserved=0, *is_cluster=True, *is_general=False), tsn=182, command_id=2, *is_reply=True) 2022-06-15 13:40:09 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] Decoded ZCL frame: MmwRadarManufCluster:set_data_response(data=TuyaCommand(status=0, tsn=111, dp=119, data=TuyaData(dp_type=, function=0, raw=b'\x01', *payload=))) 2022-06-15 13:40:09 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] Received command 0x02 (TSN 182): set_data_response(data=TuyaCommand(status=0, tsn=111, dp=119, data=TuyaData(dp_type=, function=0, raw=b'\x01', *payload=))) 2022-06-15 13:40:09 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] No datapoint handler for TuyaCommand(status=0, tsn=111, dp=119, data=TuyaData(dp_type=, function=0, raw=b'\x01', *payload=)) 2022-06-15 13:40:09 WARNING (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] No 'handle_set_data_response' tuya handler found for set_data_response(data=TuyaCommand(status=0, tsn=111, dp=119, data=TuyaData(dp_type=, function=0, raw=b'\x01', *payload=))) 2022-06-15 13:40:09 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] Sending reply header: ZCLHeader(frame_control=FrameControl(frame_type=, is_manufacturer_specific=False, is_reply=1, disable_default_response=1, reserved=0, *is_cluster=False, *is_general=True), tsn=182, command_id=, *is_reply=True) 2022-06-15 13:40:09 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] Sending reply: Default_Response(command_id=2, status=) 2022-06-15 13:40:09 DEBUG (MainThread) [zigpy_deconz.zigbee.application] Sending Zigbee request with tsn 182 under 64 request id, data: b'18b60b0281' 2022-06-15 13:40:09 DEBUG (MainThread) [zigpy_deconz.api] 'aps_data_indication' response from , ep: 1, profile: 0x0104, cluster_id: 0xef00, data: b'09b602006f7704000101' 2022-06-15 13:40:09 DEBUG (MainThread) [zigpy_deconz.api] Command Command.aps_data_request (20, 64, , , 260, 61184, 1, b'\x18\xb6\x0b\x02\x81', , 0) 2022-06-15 13:40:09 DEBUG (MainThread) [zigpy_deconz.api] APS data request response: [2, , 64] 2022-06-15 13:40:09 DEBUG (MainThread) [zigpy_deconz.api] Device state changed response: [, 0] 2022-06-15 13:40:09 DEBUG (MainThread) [zigpy_deconz.api] Command Command.aps_data_confirm (0,) 2022-06-15 13:40:09 DEBUG (MainThread) [zigpy_deconz.api] APS data confirm response for request with id 64: 00 2022-06-15 13:40:09 DEBUG (MainThread) [zigpy_deconz.api] Request id: 0x40 'aps_data_confirm' for , status: 0x00 2022-06-15 13:40:09 DEBUG (MainThread) [zigpy_deconz.api] Device state changed response: [, 0] 2022-06-15 13:40:09 DEBUG (MainThread) [zigpy_deconz.api] Command Command.aps_data_indication (1, 1) 2022-06-15 13:40:09 DEBUG (MainThread) [zigpy_deconz.api] APS data indication response: [33, , , 1, , 1, 260, 61184, b'\t\xb7\x02\x00p\x8d\x04\x00\x01\x02', 0, 175, 255, 202, 254, 18, 8, -40] 2022-06-15 13:40:09 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] Received ZCL frame: b'\t\xb7\x02\x00p\x8d\x04\x00\x01\x02' 2022-06-15 13:40:09 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] Decoded ZCL frame header: ZCLHeader(frame_control=FrameControl(frame_type=, is_manufacturer_specific=0, is_reply=1, disable_default_response=0, reserved=0, *is_cluster=True, *is_general=False), tsn=183, command_id=2, *is_reply=True) 2022-06-15 13:40:09 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] Decoded ZCL frame: MmwRadarManufCluster:set_data_response(data=TuyaCommand(status=0, tsn=112, dp=141, data=TuyaData(dp_type=, function=0, raw=b'\x02', *payload=))) 2022-06-15 13:40:09 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] Received command 0x02 (TSN 183): set_data_response(data=TuyaCommand(status=0, tsn=112, dp=141, data=TuyaData(dp_type=, function=0, raw=b'\x02', *payload=))) 2022-06-15 13:40:09 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] No datapoint handler for TuyaCommand(status=0, tsn=112, dp=141, data=TuyaData(dp_type=, function=0, raw=b'\x02', *payload=)) 2022-06-15 13:40:09 WARNING (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] No 'handle_set_data_response' tuya handler found for set_data_response(data=TuyaCommand(status=0, tsn=112, dp=141, data=TuyaData(dp_type=, function=0, raw=b'\x02', *payload=))) 2022-06-15 13:40:09 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] Sending reply header: ZCLHeader(frame_control=FrameControl(frame_type=, is_manufacturer_specific=False, is_reply=1, disable_default_response=1, reserved=0, *is_cluster=False, *is_general=True), tsn=183, command_id=, *is_reply=True) 2022-06-15 13:40:09 DEBUG (MainThread) [zigpy.zcl] [0x19E7:1:0xef00] Sending reply: Default_Response(command_id=2, status=) 2022-06-15 13:40:09 DEBUG (MainThread) [zigpy_deconz.zigbee.application] Sending Zigbee request with tsn 183 under 65 request id, data: b'18b70b0281' 2022-06-15 13:40:09 DEBUG (MainThread) [zigpy_deconz.api] 'aps_data_indication' response from , ep: 1, profile: 0x0104, cluster_id: 0xef00, data: b'09b70200708d04000102' 2022-06-15 13:40:09 DEBUG (MainThread) [zigpy_deconz.api] Command Command.aps_data_request (20, 65, , , 260, 61184, 1, b'\x18\xb7\x0b\x02\x81', , 0) 2022-06-15 13:40:09 DEBUG (MainThread) [zigpy_deconz.api] APS data request response: [2, , 65] 2022-06-15 13:40:09 DEBUG (MainThread) [zigpy_deconz.api] Device state changed response: [, 0] 2022-06-15 13:40:09 DEBUG (MainThread) [zigpy_deconz.api] Command Command.aps_data_confirm (0,) 2022-06-15 13:40:09 DEBUG (MainThread) [zigpy_deconz.api] APS data confirm response for request with id 65: 00 2022-06-15 13:40:09 DEBUG (MainThread) [zigpy_deconz.api] Request id: 0x41 'aps_data_confirm' for , status: 0x00 ```
javicalle commented 2 years ago

I would say that DP_119 can be occupancy or movement (whatever device report). I have updated the quirk in my previous comment. DP_119 is mapped to TuyaOccupancySensing and DP_141 as a MmwRadarManufCluster attribute.

wryandginger commented 2 years ago

Your updated quirk crashed ZHA for me.

UI Logger:
Logger: homeassistant.config_entries
Source: custom_zha_quirks/TuyaMMWSensor.py:81
First occurred: 3:13:12 PM (1 occurrences)
Last logged: 3:13:12 PM

Error setting up entry ConBee II, s/n:  - dresden elektronik ingenieurtechnik GmbH for zha
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/config_entries.py", line 339, in async_setup
    result = await component.async_setup_entry(hass, self)
  File "/usr/src/homeassistant/homeassistant/components/zha/__init__.py", line 99, in async_setup_entry
    setup_quirks(config)
  File "/usr/local/lib/python3.9/site-packages/zhaquirks/__init__.py", line 409, in setup
    importer.find_module(modname).load_module(modname)
  File "<frozen importlib._bootstrap_external>", line 529, in _check_name_wrapper
  File "<frozen importlib._bootstrap_external>", line 1029, in load_module
  File "<frozen importlib._bootstrap_external>", line 854, in load_module
  File "<frozen importlib._bootstrap>", line 274, in _load_module_shim
  File "<frozen importlib._bootstrap>", line 711, in _load
  File "<frozen importlib._bootstrap>", line 680, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 850, in exec_module
  File "<frozen importlib._bootstrap>", line 228, in _call_with_frames_removed
  File "/config/custom_zha_quirks/TuyaMMWSensor.py", line 57, in <module>
    class MmwRadarManufCluster(TuyaMCUCluster):
  File "/config/custom_zha_quirks/TuyaMMWSensor.py", line 81, in MmwRadarManufCluster
    1: DPToAttributeMapping(
TypeError: __init__() missing 1 required positional argument: 'dp_type'
javicalle commented 2 years ago

Ouuuuch... fixed.

wryandginger commented 2 years ago

Hurrah! It appears to work! Thank you so much @javicalle!!

javicalle commented 2 years ago

It will be great if you can get the list of the used DPs. It could be that DP_141 can be "sensing range" with values from 0-2 or maybe more.

Device is identified as 'mains powered', but if it have a battery maybe there is also a DP for battery reporting.

wryandginger commented 2 years ago

Is there a specific way you'd like me to do that?

Poking around the MmWRadarManufCluster Attributes, here's what I found: DP_102 = 48 DP_105 = enum8.undefined_0x05 DP_141 = enum8.undefined_0x02

All other DPs are None. The sensor is only microUSB powered but it does act as a router

wryandginger commented 2 years ago

So I got out a Tuya zigbee hub and I decided to do some more digging:

Screen Shots ![IMG-0762](https://user-images.githubusercontent.com/77764889/174418877-96675fdd-4c7b-43af-896c-4ee8b4c06fc7.PNG) ![IMG-0763](https://user-images.githubusercontent.com/77764889/174418880-a02db00c-6f6a-4488-9f4d-54090359556c.PNG) ![IMG-0764](https://user-images.githubusercontent.com/77764889/174418881-1ba55330-cdca-4501-80fc-27241c59bb17.PNG) ![IMG-0765](https://user-images.githubusercontent.com/77764889/174418883-444ec8e0-b277-489d-a4bc-cb63531dbc6b.PNG) ![IMG-0766](https://user-images.githubusercontent.com/77764889/174418884-90b5a2b2-3f5f-4095-8b22-768baf1fe186.PNG)

If you press "device lookup" the lights flash red and blue. It's kinda cool. I really wish I could turn them off completely.

DP 102 for sure is "induction time" which I'm pretty sure is just the cooldown for the motion sensor. It can be any number between 24 seconds and 300 seconds.

So if DP 119 is occupancy, I would guess 105 is maybe distance sensitivity, and 141 is movement/stillness? From what I can tell 102, 105, 119, and 141 are the only DPs being used.

javicalle commented 2 years ago

Pretty good findings!

I agree that DP_102 would be that 'induction time", and the units could be seconds. Device reports for DP_105 and DP_141 are enumerations. Can you get the list of possibles values? They are needed to map between values (0-N) and significance (0.5m, 1m, 3m, ...)

During the weekend I will try to propose a cleaner quirk.

wryandginger commented 2 years ago

Pretty good findings!

I agree that DP_102 would be that 'induction time", and the units could be seconds. Device reports for DP_105 and DP_141 are enumerations. Can you get the list of possibles values? They are needed to map between values (0-N) and significance (0.5m, 1m, 3m, ...)

During the weekend I will try to propose a cleaner quirk.

DP_102 is seconds and can be any value from 24 to 300. DP_105 might be target distance. Target distance for the device is 0.5 - 5 meters in 0.5 meter (0.5, 1, 1.5, 2, 2.5.....5) increments.

If DP_141 is the motion data, it might be something like this (see Tuya Solution Center doc for a similar device) :

Induction stateRequired | presence_state | Report Only | Enum | Enum Value: none, presence, peaceful, small_move, large_move -- | -- | -- | -- | --

with 0 = none, 1 = presence, 2= peaceful, 3 = small, 4 = large?

Though I wonder if this motion data is on DP_119? Because if DP_119 is also ENUM then it could have a value higher than 1, right?

javicalle commented 2 years ago

That is the new quirk version:

ts0601_radar.py ```python """Tuya mmWave sensor quirk.""" from typing import Dict from zigpy.profiles import zha from zigpy.quirks import CustomDevice import zigpy.types as t from zigpy.zcl.clusters.general import Basic, GreenPowerProxy, Groups, Ota, Scenes, Time from zigpy.zcl.clusters.measurement import OccupancySensing from zhaquirks.const import ( DEVICE_TYPE, ENDPOINTS, INPUT_CLUSTERS, MODELS_INFO, OUTPUT_CLUSTERS, PROFILE_ID, ) from zhaquirks.tuya import TuyaLocalCluster, TuyaNewManufCluster from zhaquirks.tuya.mcu import DPToAttributeMapping, TuyaDPType, TuyaMCUCluster class RangeSensing(t.enum8): """Radar range sensing enum.""" _0.5_M = 0x00 _1.0_M = 0x01 _1.5_M = 0x02 _2.0_M = 0x03 _2.5_M = 0x04 _3.0_M = 0x05 _3.5_M = 0x06 _4.0_M = 0x07 _4.5_M = 0x08 _5.0_M = 0x09 class MotionType(t.enum8): """Type of motion detected enum.""" NONE = 0x00 PRESENCE = 0x01 PEACEFULL = 0x02 SMALL_MOVE = 0x03 LARGE_MOVE = 0x04 class TuyaOccupancySensing(OccupancySensing, TuyaLocalCluster): """Tuya local OccupancySensing cluster.""" class MmwRadarManufCluster(TuyaMCUCluster): """Neo manufacturer cluster.""" attributes = TuyaMCUCluster.attributes.copy() attributes.update( { # ramdom attribute IDs 0xEF66: ("induction_delay", t.uint32_t, True), 0xEF69: ("target_distance", RangeSensing, True), 0xEF8D: ("motion_type", MotionType, True), } ) dp_to_attribute: Dict[int, DPToAttributeMapping] = { 102: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "induction_delay", dp_type=TuyaDPType.VALUE, ), 105: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "target_distance", dp_type=TuyaDPType.ENUM, ), 119: DPToAttributeMapping( TuyaOccupancySensing.ep_attribute, "occupancy", dp_type=TuyaDPType.ENUM, ), 141: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "motion_type", dp_type=TuyaDPType.ENUM, ), } data_point_handlers = { 102: "_dp_2_attr_update", 105: "_dp_2_attr_update", 119: "_dp_2_attr_update", 141: "_dp_2_attr_update", } class MmwRadarMotionGPP(CustomDevice): """Millimeter wave occupancy sensor.""" signature = { # endpoint=1, profile=260, device_type=81, device_version=1, # input_clusters=[4, 5, 61184, 0], output_clusters=[25, 10]) MODELS_INFO: [ ("_TZE200_9qayzqa8", "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: { #

Some of the changes:

  • removed unused DPS
  • removed unused entities (illuminance, analog input)
  • added Enums for known (?) values

You should be able to modify the sensor sensitivity and cooldown value from the device view, "admin clusters" option, target_distance and induction_delay attributes You should also be able to query the type of motion from the motion_type attribute. Some changes have been made in HA to be able to show these values as entities, but I don't know how to do it.

wryandginger commented 2 years ago

hmm. It didn't like that.

2022-06-19 13:50:02 DEBUG (MainThread) [zhaquirks] Loading custom quirks module MMWTest
2022-06-19 13:50:02 ERROR (MainThread) [homeassistant.config_entries] Error setting up entry ConBee II, s/n:  - dresden elektronik ingenieurtechnik GmbH for zha
Traceback (most recent call last):
File "/usr/src/homeassistant/homeassistant/config_entries.py", line 339, in async_setup
result = await component.async_setup_entry(hass, self)
File "/usr/src/homeassistant/homeassistant/components/zha/__init__.py", line 99, in async_setup_entry
setup_quirks(config)
File "/usr/local/lib/python3.9/site-packages/zhaquirks/__init__.py", line 409, in setup
importer.find_module(modname).load_module(modname)
File "<frozen importlib._bootstrap_external>", line 529, in _check_name_wrapper
File "<frozen importlib._bootstrap_external>", line 1029, in load_module
File "<frozen importlib._bootstrap_external>", line 854, in load_module
File "<frozen importlib._bootstrap>", line 274, in _load_module_shim
File "<frozen importlib._bootstrap>", line 711, in _load
File "<frozen importlib._bootstrap>", line 680, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 846, in exec_module
File "<frozen importlib._bootstrap_external>", line 983, in get_code
File "<frozen importlib._bootstrap_external>", line 913, in source_to_code
File "<frozen importlib._bootstrap>", line 228, in _call_with_frames_removed
File "/config/custom_zha_quirks/MMWTest.py", line 26
_0.5_M = 0x00
^
SyntaxError: invalid decimal literal
wryandginger commented 2 years ago

I replaced the range sensing with this and was able to get ZHA to start up.

    CEILING_R0_5M = 0x00
    CEILING_R1_0M = 0x01
    CEILING_R1_5M = 0x02
    CEILING_R2_0M = 0x03
    CEILING_R2_5M = 0x04
    CEILING_R3_0M = 0x05
    CEILING_R3_5M = 0x06
    CEILING_R4_0M = 0x07
    CEILING_R4_5M = 0x08
    CEILING_R5_0M = 0x09

I can get induction_time to read 48, but I can't write a new value. target_distance is still reporting 5 (which is the same as _3.0_M, default) and I can't write a new value. motion_type is coming back as: enum8.undefined_0x03 when there's little movement, enum8.undefined_0x02 when there's activity (which seems backwards), and 1 when there's no motion for certain.

javicalle commented 2 years ago

hmm. It didn't like that.

Ouuuuch, sorry for that. My local tests didn't catch these errors.

I can get induction_time to read 48, but I can't write a new value. target_distance is still reporting 5 (which is the same as _3.0_M, default) and I can't write a new value.

I would try with the NoManufacturerCluster implementation.

motion_type is coming back as: enum8.undefined_0x03 when there's little movement, enum8.undefined_0x02

These values are not the expected ones. I would expect something like RangeSensing.CEILING_R3_0M and MotionType.PEACEFULL, but not enum8.undefined_0x03... I have added converters for these DPs:

        105: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "target_distance",
            dp_type=TuyaDPType.ENUM,
            converter=lambda x: RangeSensing(x),
        ),
        .../...
        141: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "motion_type",
            dp_type=TuyaDPType.ENUM,
            converter=lambda x: MotionType(x),
        ),

The new version would be:

ts0601_radar.py ```python """Tuya mmWave sensor quirk.""" from typing import Dict from zigpy.profiles import zha from zigpy.quirks import CustomDevice import zigpy.types as t from zigpy.zcl.clusters.general import Basic, GreenPowerProxy, Groups, Ota, Scenes, Time from zigpy.zcl.clusters.measurement import OccupancySensing from zhaquirks.const import ( DEVICE_TYPE, ENDPOINTS, INPUT_CLUSTERS, MODELS_INFO, OUTPUT_CLUSTERS, PROFILE_ID, ) from zhaquirks.tuya import NoManufacturerCluster, TuyaLocalCluster, TuyaNewManufCluster from zhaquirks.tuya.mcu import DPToAttributeMapping, TuyaDPType, TuyaMCUCluster class RangeSensing(t.enum8): """Radar range sensing enum.""" METERS_0_5 = 0x00 METERS_1_0 = 0x01 METERS_1_5 = 0x02 METERS_2_0 = 0x03 METERS_2_5 = 0x04 METERS_3_0 = 0x05 METERS_3_5 = 0x06 METERS_4_0 = 0x07 METERS_4_5 = 0x08 METERS_5_0 = 0x09 class MotionType(t.enum8): """Type of motion detected enum.""" NONE = 0x00 PRESENCE = 0x01 PEACEFULL = 0x02 SMALL_MOVE = 0x03 LARGE_MOVE = 0x04 class TuyaOccupancySensing(OccupancySensing, TuyaLocalCluster): """Tuya local OccupancySensing cluster.""" class MmwRadarManufCluster(NoManufacturerCluster, TuyaMCUCluster): """Neo manufacturer cluster.""" attributes = TuyaMCUCluster.attributes.copy() attributes.update( { # ramdom attribute IDs 0xEF66: ("induction_delay", t.uint32_t, True), 0xEF69: ("target_distance", RangeSensing, True), 0xEF8D: ("motion_type", MotionType, True), } ) dp_to_attribute: Dict[int, DPToAttributeMapping] = { 102: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "induction_delay", dp_type=TuyaDPType.VALUE, ), 105: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "target_distance", dp_type=TuyaDPType.ENUM, converter=lambda x: RangeSensing(x), ), 119: DPToAttributeMapping( TuyaOccupancySensing.ep_attribute, "occupancy", dp_type=TuyaDPType.ENUM, ), 141: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "motion_type", dp_type=TuyaDPType.ENUM, converter=lambda x: MotionType(x), ), } data_point_handlers = { 102: "_dp_2_attr_update", 105: "_dp_2_attr_update", 119: "_dp_2_attr_update", 141: "_dp_2_attr_update", } class MmwRadarMotionGPP(CustomDevice): """Millimeter wave occupancy sensor.""" signature = { # endpoint=1, profile=260, device_type=81, device_version=1, # input_clusters=[4, 5, 61184, 0], output_clusters=[25, 10]) MODELS_INFO: [ ("_TZE200_9qayzqa8", "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: { #
javicalle commented 2 years ago

I have been looking at the AnalogOutput cluster and I think that can be usefull here. Would you try with this other version:

"""Tuya mmWave sensor quirk."""

from typing import Dict

from zigpy.profiles import zha
from zigpy.quirks import CustomDevice
import zigpy.types as t
from zigpy.zcl.clusters.general import (
    AnalogOutput,
    Basic,
    GreenPowerProxy,
    Groups,
    Ota,
    Scenes,
    Time,
)
from zigpy.zcl.clusters.measurement import OccupancySensing

from zhaquirks.const import (
    DEVICE_TYPE,
    ENDPOINTS,
    INPUT_CLUSTERS,
    MODELS_INFO,
    OUTPUT_CLUSTERS,
    PROFILE_ID,
)
from zhaquirks.tuya import NoManufacturerCluster, TuyaLocalCluster, TuyaNewManufCluster
from zhaquirks.tuya.mcu import (
    DPToAttributeMapping,
    TuyaAttributesCluster,
    TuyaDPType,
    TuyaMCUCluster,
)

class RangeSensing(t.enum8):
    """Radar range sensing enum."""

    METERS_0_5 = 0x00
    METERS_1_0 = 0x01
    METERS_1_5 = 0x02
    METERS_2_0 = 0x03
    METERS_2_5 = 0x04
    METERS_3_0 = 0x05
    METERS_3_5 = 0x06
    METERS_4_0 = 0x07
    METERS_4_5 = 0x08
    METERS_5_0 = 0x09

class MotionType(t.enum8):
    """Type of motion detected enum."""

    NONE = 0x00
    PRESENCE = 0x01
    PEACEFULL = 0x02
    SMALL_MOVE = 0x03
    LARGE_MOVE = 0x04

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

class CooldownTime(TuyaAttributesCluster, AnalogOutput):
    """AnalogOutput cluster for cooldown time."""

    def __init__(self, *args, **kwargs):
        """Init."""
        super().__init__(*args, **kwargs)
        self._update_attribute(
            self.attributes_by_name["description"].id, "Cooldown time"
        )
        self._update_attribute(
            self.attributes_by_name["max_present_value"].id,
            300,
        )
        self._update_attribute(
            self.attributes_by_name["min_present_value"].id,
            24,
        )
        self._update_attribute(self.attributes_by_name["resolution"].id, 1)
        # self._update_attribute(self.attributes_by_name["application_type"].id, 0 << 16)
        self._update_attribute(
            self.attributes_by_name["engineering_units"].id, 73
        )  # 73: seconds

class MmwRadarManufCluster(NoManufacturerCluster, TuyaMCUCluster):
    """Neo manufacturer cluster."""

    attributes = TuyaMCUCluster.attributes.copy()
    attributes.update(
        {
            # ramdom attribute IDs
            # 0xEF66: ("induction_delay", t.uint32_t, True),
            0xEF69: ("target_distance", RangeSensing, True),
            0xEF8D: ("motion_type", MotionType, True),
        }
    )

    dp_to_attribute: Dict[int, DPToAttributeMapping] = {
        102: DPToAttributeMapping(
            CooldownTime.ep_attribute,
            "present_value",
            dp_type=TuyaDPType.VALUE,
        ),
        105: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "target_distance",
            dp_type=TuyaDPType.ENUM,
            converter=lambda x: RangeSensing(x),
        ),
        119: DPToAttributeMapping(
            TuyaOccupancySensing.ep_attribute,
            "occupancy",
            dp_type=TuyaDPType.ENUM,
        ),
        141: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "motion_type",
            dp_type=TuyaDPType.ENUM,
            converter=lambda x: MotionType(x),
        ),
    }

    data_point_handlers = {
        102: "_dp_2_attr_update",
        105: "_dp_2_attr_update",
        119: "_dp_2_attr_update",
        141: "_dp_2_attr_update",
    }

class MmwRadarMotionGPP(CustomDevice):
    """Millimeter wave occupancy sensor."""

    signature = {
        #  endpoint=1, profile=260, device_type=81, device_version=1,
        #  input_clusters=[4, 5, 61184, 0], output_clusters=[25, 10])
        MODELS_INFO: [
            ("_TZE200_9qayzqa8", "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,
                    CooldownTime,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            242: {
                PROFILE_ID: 41440,
                DEVICE_TYPE: 97,
                INPUT_CLUSTERS: [],
                OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
            },
        }
    }

It is expected to generate a new number entity where you can set the cooldown_time (induction relay or whatever it is called) attribute.

Thanks in advanced.

wryandginger commented 2 years ago

hmm. It didn't like that.

Ouuuuch, sorry for that. My local tests didn't catch these errors.

I can get induction_time to read 48, but I can't write a new value. target_distance is still reporting 5 (which is the same as _3.0_M, default) and I can't write a new value.

I would try with the NoManufacturerCluster implementation.

motion_type is coming back as: enum8.undefined_0x03 when there's little movement, enum8.undefined_0x02

These values are not the expected ones. I would expect something like RangeSensing.CEILING_R3_0M and MotionType.PEACEFULL, but not enum8.undefined_0x03... I have added converters for these DPs:

The new version would be:

ts0601_radar.py

This version seems to be working the best! I have induction_time, motion_type, and target_distance.

The induction_time and target_distance are all accepting values. Though, with target_distance you have to send 1 to the cluster to get RangeSensing.METERS_1_0 as a reply.

The motion_type is a little wonky -- the names are changing, but they appear to be off slightly:

MotionType.PRESENCE shows when there's nobody in the room. MotionType.PEACEFULL happens on entry/activity MotionType.SMALL_MOVE happens when I'm sitting.

I would assume just changing the text in the quirk to None, Active, Presence or something would probably fix that:

class MotionType(t.enum8):
    """Type of motion detected enum."""

    EMPTY = 0x00
    NONE = 0x01
    ACTIVE = 0x02
    PRESENCE = 0x03
    UNKNOWN = 0x04

Thank you for all of your help on this @javicalle! This is really helping me learn and understand ZHA quirks more, so I greatly appreciate your hard work and help on this.

wryandginger commented 2 years ago

I have been looking at the AnalogOutput cluster and I think that can be usefull here. Would you try with this other version:

It is expected to generate a new number entity where you can set the cooldown_time (induction relay or whatever it is called) attribute.

Thanks in advanced.

This didn't seem to work. I got errors with DP 102:

Logger: zigpy.zcl
Source: /usr/local/lib/python3.9/site-packages/zhaquirks/tuya/__init__.py:1411 
First occurred: 9:46:44 AM (1 occurrences) 
Last logged: 9:46:44 AM

[0xD4F3:1:0xef00] No 'handle_set_data_response' tuya handler found for set_data_response(data=TuyaCommand(status=0, tsn=210, dp=102, data=TuyaData(dp_type=<TuyaDPType.VALUE: 2>, function=0, raw=b'\x18\x00\x00\x00', *payload=24)))

I'll go back to the previous version

javicalle commented 2 years ago

The induction_time and target_distance are all accepting values. Though, with target_distance you have to send 1 to the cluster to get RangeSensing.METERS_1_0 as a reply.

Yes, you need to set the value number (0-9 or 0x00-0x09). And when you get the value, you will also get the 'label' wich would give to user some clue about the value meaning. (At least this is the intention).

I would assume just changing the text in the quirk to None, Active, Presence or something would probably fix that

If not documented, setting 'labels' to values usually is a trial and error process. Only someone testing the device can get the meanings to values. You can also implement the quirk without an enumeration, but then users will have to 'guess' the meaning of each value.

This didn't seem to work. I got errors with DP 102:

Ouuuch^2 I have fixed the code. Just added the CooldownTime cluster in the replacement part:

    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,
                    CooldownTime,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            242: {
                PROFILE_ID: 41440,
                DEVICE_TYPE: 97,
                INPUT_CLUSTERS: [],
                OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
            },
        }
    }

Would you be so kind as to try the fixed version?

Thanks in advanced.

wryandginger commented 2 years ago

WOW!!

Screen Shot 2022-06-20 at 12 17 38 PM

That seems to work really well. The device instantly responds to changing the cooldown time.

javicalle commented 2 years ago

Thanks for trying it out. I apreciate it.

wryandginger commented 2 years ago

So far this updated quirk you wrote is working great and it's doing more than I could have hoped, thank you again!

I was reading through this and was wondering if it would be possible to use the IAS_Zone cluster to have the target_distance show up under controls?

For example, can we put the target distance to CurrentZoneSensitivityLevel and NumberOfZoneSensitivityLevels to 10? (see page 460)

ZoneType / E_CLD_IASZONE_TYPE_MOTION_SENSOR (see page 458) looks like the motion alarms (intrusion/presence) might work for the motion type too, maybe?

javicalle commented 2 years ago

if it would be possible to use the IAS_Zone cluster

Yes, it should be possible. Same way that we have implemented the quirk with a OccupancySensing, probably we can use a IasZone cluster. It's just a matter of finding the cluster that best fits the functionality of the device

to have the target_distance show up under controls

Meeeeeh, probably not or not in the way you'd like.

can we put the target distance to CurrentZoneSensitivityLevel and NumberOfZoneSensitivityLevels to 10?

Yes for sure:

But this would not be show in HA as control entities it will be just an attributes of cluster.

The ZHA integration is very limited yet. It's growing big and fast, but some less common use cases are harder to find implemented. This would be one of this cases. Anyone can implement it, but it not my area and I can't give support in the HA implementation part.

More references:

github-actions[bot] commented 1 year ago

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.

wryandginger commented 1 year ago

Here's an updated quirk for this. I literally have no clue how to put this as a PR, could someone do that (or tell me what to do?)

"""Tuya mmWave sensor quirk."""

from typing import Dict

from zigpy.profiles import zha
from zigpy.quirks import CustomDevice
import zigpy.types as t
from zigpy.zcl.clusters.general import (
    AnalogOutput,
    Basic,
    GreenPowerProxy,
    Groups,
    Ota,
    Scenes,
    Time,
)
from zigpy.zcl.clusters.measurement import OccupancySensing

from zhaquirks.const import (
    DEVICE_TYPE,
    ENDPOINTS,
    INPUT_CLUSTERS,
    MODELS_INFO,
    OUTPUT_CLUSTERS,
    PROFILE_ID,
)
from zhaquirks.tuya import NoManufacturerCluster, TuyaLocalCluster, TuyaNewManufCluster
from zhaquirks.tuya.mcu import (
    DPToAttributeMapping,
    TuyaAttributesCluster,
    TuyaMCUCluster,
)

class RangeSensing(t.enum8):
    """Radar range sensing enum."""

    METERS_0_5 = 0x00
    METERS_1_0 = 0x01
    METERS_1_5 = 0x02
    METERS_2_0 = 0x03
    METERS_2_5 = 0x04
    METERS_3_0 = 0x05
    METERS_3_5 = 0x06
    METERS_4_0 = 0x07
    METERS_4_5 = 0x08
    METERS_5_0 = 0x09

class MotionType(t.enum8):
    """Type of motion detected enum."""

    ERROR_LOW = 0x00
    CLEAR = 0x01
    ACTIVE = 0x02
    PRESENCE = 0x03
    ERROR_HIGH = 0x04

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

class CooldownTime(TuyaAttributesCluster, AnalogOutput):
    """AnalogOutput cluster for cooldown time."""

    def __init__(self, *args, **kwargs):
        """Init."""
        super().__init__(*args, **kwargs)
        self._update_attribute(
            self.attributes_by_name["description"].id, "Cooldown time"
        )
        self._update_attribute(
            self.attributes_by_name["max_present_value"].id,
            300,
        )
        self._update_attribute(
            self.attributes_by_name["min_present_value"].id,
            24,
        )
        self._update_attribute(self.attributes_by_name["resolution"].id, 1)
        # self._update_attribute(self.attributes_by_name["application_type"].id, 0 << 16)
        self._update_attribute(
            self.attributes_by_name["engineering_units"].id, 73
        )  # 73: seconds

class MmwRadarManufCluster(NoManufacturerCluster, TuyaMCUCluster):
    """Neo manufacturer cluster."""

    attributes = TuyaMCUCluster.attributes.copy()
    attributes.update(
        {
            # ramdom attribute IDs
            # 0xEF66: ("induction_delay", t.uint32_t, True),
            0xEF69: ("target_distance", RangeSensing, True),
            0xEF8D: ("motion_type", MotionType, True),
        }
    )

    dp_to_attribute: Dict[int, DPToAttributeMapping] = {
        102: DPToAttributeMapping(
            CooldownTime.ep_attribute,
            "present_value",
        ),
        105: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "target_distance",
            converter=lambda x: RangeSensing(x),
        ),
        119: DPToAttributeMapping(
            TuyaOccupancySensing.ep_attribute,
            "occupancy",
        ),
        141: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "motion_type",
            converter=lambda x: MotionType(x),
        ),
    }

    data_point_handlers = {
        102: "_dp_2_attr_update",
        105: "_dp_2_attr_update",
        119: "_dp_2_attr_update",
        141: "_dp_2_attr_update",
    }

class MmwRadarMotionGPP(CustomDevice):
    """Millimeter wave occupancy sensor."""

    signature = {
        #  endpoint=1, profile=260, device_type=81, device_version=1,
        #  input_clusters=[4, 5, 61184, 0], output_clusters=[25, 10])
        MODELS_INFO: [
            ("_TZE200_9qayzqa8", "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,
                    CooldownTime,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            242: {
                PROFILE_ID: 41440,
                DEVICE_TYPE: 97,
                INPUT_CLUSTERS: [],
                OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
            },
        }
    }