Skip to content
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

Add support for ZY-M100 wall mount #1928

Merged
merged 1 commit into from
Nov 19, 2022
Merged

Add support for ZY-M100 wall mount #1928

merged 1 commit into from
Nov 19, 2022

Conversation

blakadder
Copy link
Contributor

@coveralls
Copy link

coveralls commented Nov 16, 2022

Pull Request Test Coverage Report for Build 3486299868

  • 0 of 0 changed or added relevant lines in 0 files are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage remained the same at 82.686%

Totals Coverage Status
Change from base Build 3462174808: 0.0%
Covered Lines: 6323
Relevant Lines: 7647

💛 - Coveralls

@codecov-commenter
Copy link

codecov-commenter commented Nov 16, 2022

Codecov Report

Base: 82.68% // Head: 82.68% // No change to project coverage 👍

Coverage data is based on head (08d1877) compared to base (59c098d).
Patch has no changes to coverable lines.

Additional details and impacted files
@@           Coverage Diff           @@
##              dev    #1928   +/-   ##
=======================================
  Coverage   82.68%   82.68%           
=======================================
  Files         245      245           
  Lines        7647     7647           
=======================================
  Hits         6323     6323           
  Misses       1324     1324           
Impacted Files Coverage Δ
zhaquirks/tuya/ts004f.py 80.64% <ø> (ø)
zhaquirks/tuya/ts0601_motion.py 100.00% <ø> (ø)

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report at Codecov.
📢 Do you have feedback about the report comment? Let us know in this issue.

@KevinVanthuyne
Copy link

KevinVanthuyne commented Nov 18, 2022

I have one of these sensors with the updated manufacturer, so I can +1 this

@dmulcahey dmulcahey merged commit e18e239 into zigpy:dev Nov 19, 2022
@KevinVanthuyne
Copy link

Quick question, it seems that this sensor is now indeed detected by ZHA under Home Assistant, but it only exposes "presence", while it should also have
sensitivity, set delay, set range and illuminance. Is that still an issue or why am I not seeing those properties?

@blakadder
Copy link
Contributor Author

Those aren't included in the quirk

@KevinVanthuyne
Copy link

Would it be hard to add those? I'd like to contribute to that if possible but haven't made a quirk yet and it looks a bit intimidating

@mikosoft83
Copy link

I just received this sensor and I +1 Kevin's question, I'd gladly contribute to the quirk but I have almost zero knowledge of python and I don't really understand how the quirks file works at all (and yes, I tried reading the docu, didn't help me at all).

@ark-
Copy link

ark- commented Dec 5, 2022

Hi,

I been messing with the clusters in "Manage Zigbee Device". I have also found on Tasmota docs that they explain what all the different DP mappings are, here: https://templates.blakadder.com/ZY-M100.html.

dpID Function Datatype Additional Comments
1 Presence state Enum 0 = none, 1 = presence
2 Sensitivity Integer Value range 0 .. 9
3 Near Detection Integer Value Range: 0 .. 1000, 1 = 0.01m
4 Far Detection Integer Value Range: 0 .. 1000, 1 = 0.01m
6 Self Check Result Enum 0 = checking, 1 = check_success, 2 = check_failure, 3 = others, 4 = comm_fault, 5 = radar_fault
9 Target Distance Integer Value Range: 0 .. 1000, 1 = 1cm
101 Detection Delay Integer Value Range: 0 .. 100, 1 = 0.10s
102 Fading Time Integer Value Range: 10 .. 1500, 1 = 1s
103 Unknown String Value is empty and cannot be changed
104 Light intensity Integer Value Range: 0 - 2000 in lx
106 Reset button Bool Reset button changes states between 0 and 1

When I read dp_2 I get a reading of 7. So 7/10 sensitivity, that's great. However if I try to write it, to a 5 for example, it doesn't update. Is there any reason these are read only?

I also had a look at the code which implements these dp_x https://github.com/zigpy/zha-device-handlers/blob/dev/zhaquirks/tuya/ts0601_motion.py and I'm currently trying to follow through how these appear as entities in HomeAssistant.

@mikosoft83
Copy link

@ark- there is a docu that explains how to build a custom quirk:
https://pypi.org/project/zha-quirks/
But when I compare what's required vs. what's actually implemented in the tuya motion it doesn't make much sense to me. E.g. the guide says there need to be instances of Bus but there are one for the MmwRadarMotion.
On the other hand there already seems to be a whole custom cluster implemented for all the setup parameters but none of them shows in HA.
This complemented by my zero knowledge of python makes it seem like black magic more than coding.

@mikosoft83
Copy link

Okay, I think I begin to understand that. This line:

dp_to_attribute: Dict[int, DPToAttributeMapping] = {

translates dp_x to something meaningful. For example, occupancy (dp_1) gets put into TuyaOccupancySensing which is an extension of OccupancySensing and that seems to be "automatically" translated into Occupancy entity in HA. Same for illuminance.
There is also AnalogInput present_value (which is actually detected distance to target) but it seems AnalogInput cluster does NOT get picked up by HA.
So the question is now, how do we make HA create entity from the AnalogInput?

@ark-
Copy link

ark- commented Dec 6, 2022

Thanks, I've read that guide, it's the same as the README for this repo, but like you I found it didn't quite match the implementation in this PR. I'm glad it's not just me who thinks this is a little spaghetti (though I suspect it's because I'm looking at the end of a working day). I imagine it's an unfortunate side effect of the nature of this repo: complicated, needs to be modular and has to support lots of different systems.

I also thought the mapping e.g. TuyaOccupancySensing would magically propagate up to Home Assistant, but Illuminance doesn't show up in HA for me...? So potentially there's a bug with how it paired which wouldn't surprise me as the first 2 times I paired it there was no occupancy entity either.

I also notice that my detected status sometimes gets stuck too. It will refresh if I force a cluster read on occupancy.

@mikosoft83
Copy link

Occupancy and Illuminance both work for me just fine since I connected the sensor yesterday. It's just the other settings that don't get into HA and when I tried setting them that doesn't work for me either. I assume it's because it lacks the Bus (but that would also mean it shouldn't even read them?).

I'm not sure if there are any devices at all in ZHA that support some kind of settings. If yes maybe we could try to pick those quirks apart.

@ark-
Copy link

ark- commented Dec 6, 2022

Funny, I was just thinking this. I change my Tuya Siren's "tune" using the clusters UI. Here's the relevant file: https://github.com/zigpy/zha-device-handlers/blob/dev/zhaquirks/tuya/ts0601_siren.py

@ark-
Copy link

ark- commented Dec 6, 2022

I note the addition of a write_attributes() which fills out the manufacturer ID https://github.com/zigpy/zha-device-handlers/blob/dev/zhaquirks/tuya/ts0601_siren.py#LL274C5-L279C10. Maybe we need to enter that in the UI or add a similar function to the Quirk. The question is, whats the Manufacturer ID of the ZY-M100.

Edit: Oh that just resolves to -1 in the Zigpy repo. https://github.com/zigpy/zigpy/blob/9fb06018de273cf66dde72c447b40f50ac5c7798/zigpy/zcl/foundation.py#L575. Nevermind.

It might be that the dp_to_attribute part has a type

7: DPToAttributeMapping(
            TuyaMCUSiren.ep_attribute,
            "alarm_duration",
            dp_type=TuyaDPType.VALUE, #<See here
        ),

@mikosoft83
Copy link

mikosoft83 commented Dec 6, 2022

For me the manufacturer ID is _TZE204_ztc6ggyl but maybe the same line from the siren would work just fine?

Still doesn't quite resolve the issue with the various attributes not being picked up as entities though

@mikosoft83
Copy link

The python script that creates sensors seems to be https://github.com/home-assistant/core/blob/dev/homeassistant/components/zha/sensor.py
But going through it it seems to me the supported devices are kind of hardcoded there or I just don't understand what exactly is it supposed to do

@ark-
Copy link

ark- commented Dec 6, 2022

I understand you want the sensors to show up as entities but thats only half the problem. Until the clusters can be communicated with you wont be able to change anything anyway.

Zigbee2Mqtt have the following DP mapping: https://github.com/Koenkk/zigbee-herdsman-converters/blob/master/lib/tuya.js#L834:L841. Which align to my earlier table, so I'm fairly certain I have the mapping correct.

These values are converted here: https://github.com/Koenkk/zigbee-herdsman-converters/blob/master/converters/toZigbee.js#L7011:L7031. I see no reason they cant

And sent here: https://github.com/Koenkk/zigbee-herdsman-converters/blob/master/lib/tuya.js#L1043

I don't see anything specific they're doing to write these values.

@mikosoft83
Copy link

Yep, understood
I'm thinking maybe the Tuya classes in ZHA are somehow messed up? Unfortunately I'm not able to see if that's the case

@ark-
Copy link

ark- commented Dec 7, 2022

Some excellent discussion here: #1645

I need to try that custom quirk for this device and see if it works. Will report back when I get a chance, could be a few days.

@ark-
Copy link

ark- commented Dec 9, 2022

So scanning the code from #1645, to propagate attributes up to Home Assistant, you need to add an additional endpoint for each one to the ENDPOINTS array in the replacement variable. I think trying this and seeing if it changes the values on the device would be a good start.

@aquemy
Copy link

aquemy commented Dec 12, 2022

@ark- I have both devices. I managed to use the quirk from #1645 for _TZE200_ztc6ggyl but for _TZE204_ztc6ggyl, the quirk is recognized but I can't set the values (the sliders are automatically brought back to their default value).

Did you have any success?

@ark-
Copy link

ark- commented Dec 15, 2022

I was optimistic in my "could be a few days". I've unfortunately not had a chance and not sure when I can cast my eye over this again.

@mjdegraaff
Copy link

Any idea if there will be a solution in the near future? Would be nice to get the full use out of this sensor.

@aquemy
Copy link

aquemy commented Dec 21, 2022

Any idea if there will be a solution in the near future? Would be nice to get the full use out of this sensor.

@mjdegraaff You might want to try the quirk here: #1645
I have 5 of these sensors with two different models, and they work well.

@mjdegraaff
Copy link

Is that something which is easy? I'm not that experienced with these things....

Thanks for your reply btw!

@broyuken
Copy link

broyuken commented Feb 7, 2023

I have 2 of these and I loaded up the custom quirk. The first one works fine, the second one works, except the illuminance sensor isn’t there. Both loaded the same quirk, and have the same device info.

@MattWestb
Copy link
Contributor

@broyuken Try restarting HA then sometime is ZHA being lazy loading quirks OK the adding devices and both is matching the quirk so it shall being the same.

@broyuken
Copy link

broyuken commented Feb 7, 2023

yea I restarted HA and readded it and it showed up.

@miwhitey
Copy link

I’m using _TZE204_ztc6ggyl together with the quirk @aquemy mentioned and I still have the problem, that the values cannot be set: the sliders are automatically brought back to their default value. Is there a solution for this in the meantime?

@cobirnm
Copy link

cobirnm commented Feb 21, 2023

t back to their default value. Is there a solution for this in the meantime?

you meed to apply the correction in #1928 (comment)

Never the less you have the full code below. It worked for me:

"""Tuya mmw radar occupancy sensor."""

import math
from typing import Dict, Optional, Tuple, Union

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

from zhaquirks import Bus, LocalDataCluster, MotionOnEvent
from zhaquirks.const import (
    DEVICE_TYPE,
    ENDPOINTS,
    INPUT_CLUSTERS,
    MODEL,
    MOTION_EVENT,
    OUTPUT_CLUSTERS,
    PROFILE_ID,
)

from zhaquirks.tuya import (
    NoManufacturerCluster,
    TuyaLocalCluster,
    TuyaNewManufCluster,
)
from zhaquirks.tuya.mcu import (
    TuyaDPType,
    DPToAttributeMapping,
    TuyaAttributesCluster,
    TuyaMCUCluster,
)


class TuyaMmwRadarSelfTest(t.enum8):
    """Mmw radar self test values."""
    TESTING = 0
    TEST_SUCCESS = 1
    TEST_FAILURE = 2
    OTHER = 3
    COMM_FAULT = 4
    RADAR_FAULT = 5

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

class TuyaIlluminanceMeasurement(IlluminanceMeasurement, TuyaLocalCluster):
    """Tuya local IlluminanceMeasurement cluster."""

class TuyaMmwRadarSensitivity(TuyaAttributesCluster, AnalogOutput):
    """AnalogOutput cluster for sensitivity."""

    def __init__(self, *args, **kwargs):
        """Init."""
        super().__init__(*args, **kwargs)
        self._update_attribute(
            self.attributes_by_name["description"].id, "Sensitivity"
        )
        self._update_attribute(self.attributes_by_name["min_present_value"].id, 1)
        self._update_attribute(self.attributes_by_name["max_present_value"].id, 9)
        self._update_attribute(self.attributes_by_name["resolution"].id, 1)

class TuyaMmwRadarMinRange(TuyaAttributesCluster, AnalogOutput):
    """AnalogOutput cluster for min range."""

    def __init__(self, *args, **kwargs):
        """Init."""
        super().__init__(*args, **kwargs)
        self._update_attribute(
            self.attributes_by_name["description"].id, "Min range"
        )
        self._update_attribute(self.attributes_by_name["min_present_value"].id, 0)
        self._update_attribute(self.attributes_by_name["max_present_value"].id, 950)
        self._update_attribute(self.attributes_by_name["resolution"].id, 10)
        self._update_attribute(
            self.attributes_by_name["engineering_units"].id, 118
        )  # 31: meters

class TuyaMmwRadarMaxRange(TuyaAttributesCluster, AnalogOutput):
    """AnalogOutput cluster for max range."""

    def __init__(self, *args, **kwargs):
        """Init."""
        super().__init__(*args, **kwargs)
        self._update_attribute(
            self.attributes_by_name["description"].id, "Max range"
        )
        self._update_attribute(self.attributes_by_name["min_present_value"].id, 0)
        self._update_attribute(self.attributes_by_name["max_present_value"].id, 950)
        self._update_attribute(self.attributes_by_name["resolution"].id, 10)
        self._update_attribute(
            self.attributes_by_name["engineering_units"].id, 118
        )  # 31: meters

class TuyaMmwRadarDetectionDelay(TuyaAttributesCluster, AnalogOutput):
    """AnalogOutput cluster for detection delay."""

    def __init__(self, *args, **kwargs):
        """Init."""
        super().__init__(*args, **kwargs)
        self._update_attribute(
            self.attributes_by_name["description"].id, "Detection delay"
        )
        self._update_attribute(self.attributes_by_name["min_present_value"].id, 000)
        self._update_attribute(self.attributes_by_name["max_present_value"].id, 20000)
        self._update_attribute(self.attributes_by_name["resolution"].id, 100)
        self._update_attribute(
            self.attributes_by_name["engineering_units"].id, 159
        )  # 73: seconds

class TuyaMmwRadarFadingTime(TuyaAttributesCluster, AnalogOutput):
    """AnalogOutput cluster for fading time."""

    def __init__(self, *args, **kwargs):
        """Init."""
        super().__init__(*args, **kwargs)
        self._update_attribute(
            self.attributes_by_name["description"].id, "Fading time"
        )
        self._update_attribute(self.attributes_by_name["min_present_value"].id, 0000)
        self._update_attribute(self.attributes_by_name["max_present_value"].id, 200000)
        self._update_attribute(self.attributes_by_name["resolution"].id, 1000)
        self._update_attribute(
            self.attributes_by_name["engineering_units"].id, 159
        )  # 73: seconds

class TuyaMmwRadarTargetDistance(TuyaAttributesCluster, AnalogInput):
    """AnalogInput cluster for target distance."""

    def __init__(self, *args, **kwargs):
        """Init."""
        super().__init__(*args, **kwargs)
        self._update_attribute(
            self.attributes_by_name["description"].id, "Target distance"
        )
        self._update_attribute(
            self.attributes_by_name["engineering_units"].id, 31
        )  # 31: meters



class TuyaMmwRadarCluster(NoManufacturerCluster, TuyaMCUCluster):
    """Mmw radar cluster."""
    attributes = TuyaMCUCluster.attributes.copy()
    attributes.update(
        {
            # ramdom attribute IDs
            0xEF01: ("occupancy", t.uint32_t, True),
            0xEF02: ("sensitivity", t.uint32_t, True),
            0xEF03: ("min_range", t.uint32_t, True),
            0xEF04: ("max_range", t.uint32_t, True),
            0xEF06: ("self_test", TuyaMmwRadarSelfTest, True),
            0xEF09: ("target_distance", t.uint32_t, True),
            0xEF65: ("detection_delay", t.uint32_t, True),
            0xEF66: ("fading_time", t.uint32_t, True),
            0xEF67: ("cli", t.CharacterString, True),
            0xEF68: ("illuminance", t.uint32_t, True),
        }
    )

    dp_to_attribute: Dict[int, DPToAttributeMapping] = {
        1: DPToAttributeMapping(
            TuyaOccupancySensing.ep_attribute,
            "occupancy",
            dp_type=TuyaDPType.BOOL,
        ),
        2: DPToAttributeMapping(
            TuyaMmwRadarSensitivity.ep_attribute,
            "present_value",
            dp_type=TuyaDPType.VALUE,
        ),
        3: DPToAttributeMapping(
            TuyaMmwRadarMinRange.ep_attribute,
            "present_value",
            dp_type=TuyaDPType.VALUE,
            endpoint_id=2,
            #converter=lambda x: x / 100,
            #dp_converter=lambda x: x * 100,
        ),
        4: DPToAttributeMapping(
            TuyaMmwRadarMaxRange.ep_attribute,
            "present_value",
            dp_type=TuyaDPType.VALUE,
            endpoint_id=3,
            #converter=lambda x: x / 100,
            #dp_converter=lambda x: x * 100,
        ),
        6: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "self_test",
            dp_type=TuyaDPType.ENUM,
        ),
        9: DPToAttributeMapping(
            TuyaMmwRadarTargetDistance.ep_attribute,
            "present_value",
            #converter=lambda x: x / 100,
            dp_type=TuyaDPType.VALUE,
        ),
        101: DPToAttributeMapping(
            TuyaMmwRadarDetectionDelay.ep_attribute,
            "present_value",
            dp_type=TuyaDPType.VALUE,
            converter=lambda x: x * 100,
            dp_converter=lambda x: x // 100,
            endpoint_id=4,
        ),
        102: DPToAttributeMapping(
            TuyaMmwRadarFadingTime.ep_attribute,
            "present_value",
            dp_type=TuyaDPType.VALUE,
            converter=lambda x: x * 100,
            dp_converter=lambda x: x // 100,
            endpoint_id=5,
        ),
        103: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "cli",
            dp_type=TuyaDPType.STRING,
        ),
        104: DPToAttributeMapping(
            TuyaIlluminanceMeasurement.ep_attribute,
            "measured_value",
            dp_type=TuyaDPType.VALUE,
            converter=lambda x: math.log10(x) * 10000 + 1 if x > 0 else 0,
        ),
    }

    data_point_handlers = {
        1: "_dp_2_attr_update",
        2: "_dp_2_attr_update",
        3: "_dp_2_attr_update",
        4: "_dp_2_attr_update",
        6: "_dp_2_attr_update",
        9: "_dp_2_attr_update",
        101: "_dp_2_attr_update",
        102: "_dp_2_attr_update",
        103: "_dp_2_attr_update",
        104: "_dp_2_attr_update",
    }

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

    signature = {
        #  endpoint=1, profile=260, device_type=81, device_version=1,
        #  input_clusters=[0, 4, 5, 61184], output_clusters=[25, 10]
        "models_info": [
            ("_TZE200_ar0slwnd", "TS0601"),
            ("_TZE200_sfiy5tfs", "TS0601"),
            ("_TZE200_mrf6vtua", "TS0601"),
            ("_TZE200_ztc6ggyl", "TS0601"),
            ("_TZE204_ztc6ggyl", "TS0601"),
        ],
        ENDPOINTS: {
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    TuyaNewManufCluster.cluster_id,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
        },
    }


    replacement = {
        ENDPOINTS: {
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.OCCUPANCY_SENSOR,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    TuyaMmwRadarCluster,
                    TuyaIlluminanceMeasurement,
                    TuyaOccupancySensing,
                    TuyaMmwRadarTargetDistance,
                    TuyaMmwRadarSensitivity,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            2: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
                INPUT_CLUSTERS: [
                    TuyaMmwRadarMinRange,
                ],
                OUTPUT_CLUSTERS: [],
            },
            3: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
                INPUT_CLUSTERS: [
                    TuyaMmwRadarMaxRange,
                ],
                OUTPUT_CLUSTERS: [],
            },
            4: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
                INPUT_CLUSTERS: [
                    TuyaMmwRadarDetectionDelay,
                ],
                OUTPUT_CLUSTERS: [],
            },
            5: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
                INPUT_CLUSTERS: [
                    TuyaMmwRadarFadingTime,
                ],
                OUTPUT_CLUSTERS: [],
            },
        }
    }

@lemppari
Copy link

When will this be available with a normal HA release, without quirks?

@javicalle
Copy link
Collaborator

@cobirnm could you test to replace the _update_attribute references like these ones:

        self._update_attribute(self.attributes_by_name["min_present_value"].id, 1)
        self._update_attribute(self.attributes_by_name["max_present_value"].id, 9)
        self._update_attribute(self.attributes_by_name["resolution"].id, 1)

With expressions like this others (be carefull, I have changed the _update_attribute method):

        self.update_attribute("min_present_value", 1)
        self.update_attribute("max_present_value", 9)
        self.update_attribute("resolution", 1)

All the magic would come from here:

class TuyaLocalCluster(LocalDataCluster):
"""Tuya virtual clusters.
Prevents attribute reads and writes. Attribute writes could be converted
to DataPoint updates.
"""
def update_attribute(self, attr_name: str, value: Any) -> None:
"""Update attribute by attribute name."""
try:
attr = self.attributes_by_name[attr_name]
except KeyError:
self.debug("no such attribute: %s", attr_name)
return
return self._update_attribute(attr.id, value)

@broyuken
Copy link

broyuken commented Mar 1, 2023

Did this custom quirk break on 2023.3 for anyone else?

@guolivar
Copy link

guolivar commented Mar 1, 2023

The error I got was:

Logger: homeassistant.config_entries
Source: custom_zha_quirks/zmsensor.py:43
First occurred: 11:15:39 AM (1 occurrences)
Last logged: 11:15:39 AM

Error setting up entry Sonoff Zigbee 3.0 USB Dongle Plus - Sonoff Zigbee 3.0 USB Dongle Plus for zha
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/config_entries.py", line 383, in async_setup
    result = await component.async_setup_entry(hass, self)
  File "/usr/src/homeassistant/homeassistant/components/zha/__init__.py", line 100, in async_setup_entry
    setup_quirks(config)
  File "/usr/local/lib/python3.10/site-packages/zhaquirks/__init__.py", line 409, in setup
    importer.find_module(modname).load_module(modname)
  File "<frozen importlib._bootstrap_external>", line 548, in _check_name_wrapper
  File "<frozen importlib._bootstrap_external>", line 1063, in load_module
  File "<frozen importlib._bootstrap_external>", line 888, in load_module
  File "<frozen importlib._bootstrap>", line 290, in _load_module_shim
  File "<frozen importlib._bootstrap>", line 719, in _load
  File "<frozen importlib._bootstrap>", line 688, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 883, in exec_module
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "/config/custom_zha_quirks/zmsensor.py", line 43, in <module>
    from zhaquirks.tuya.mcu import (
ImportError: cannot import name 'TuyaDPType' from 'zhaquirks.tuya.mcu' (/usr/local/lib/python3.10/site-packages/zhaquirks/tuya/mcu/__init__.py)

@broyuken
Copy link

broyuken commented Mar 1, 2023

Yep that’s the same error I got.

@buzzeddesign
Copy link

buzzeddesign commented Mar 1, 2023

Same error as well - believe this is due to this PR in the zigpy project (TuyaDPType is now automatic?)

I commented out all of the references to TuyaDPType and it began working as it did before:


import math
from typing import Dict, Optional, Tuple, Union

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

from zhaquirks import Bus, LocalDataCluster, MotionOnEvent
from zhaquirks.const import (
    DEVICE_TYPE,
    ENDPOINTS,
    INPUT_CLUSTERS,
    MODEL,
    MOTION_EVENT,
    OUTPUT_CLUSTERS,
    PROFILE_ID,
)

from zhaquirks.tuya import (
    NoManufacturerCluster,
    TuyaLocalCluster,
    TuyaNewManufCluster,
)
from zhaquirks.tuya.mcu import (
    # TuyaDPType,
    DPToAttributeMapping,
    TuyaAttributesCluster,
    TuyaMCUCluster,
)


class TuyaMmwRadarSelfTest(t.enum8):
    """Mmw radar self test values."""
    TESTING = 0
    TEST_SUCCESS = 1
    TEST_FAILURE = 2
    OTHER = 3
    COMM_FAULT = 4
    RADAR_FAULT = 5

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

class TuyaIlluminanceMeasurement(IlluminanceMeasurement, TuyaLocalCluster):
    """Tuya local IlluminanceMeasurement cluster."""

class TuyaMmwRadarSensitivity(TuyaAttributesCluster, AnalogOutput):
    """AnalogOutput cluster for sensitivity."""

    def __init__(self, *args, **kwargs):
        """Init."""
        super().__init__(*args, **kwargs)
        self._update_attribute(
            self.attributes_by_name["description"].id, "Sensitivity"
        )
        self._update_attribute(self.attributes_by_name["min_present_value"].id, 1)
        self._update_attribute(self.attributes_by_name["max_present_value"].id, 9)
        self._update_attribute(self.attributes_by_name["resolution"].id, 1)

class TuyaMmwRadarMinRange(TuyaAttributesCluster, AnalogOutput):
    """AnalogOutput cluster for min range."""

    def __init__(self, *args, **kwargs):
        """Init."""
        super().__init__(*args, **kwargs)
        self._update_attribute(
            self.attributes_by_name["description"].id, "Min range"
        )
        self._update_attribute(self.attributes_by_name["min_present_value"].id, 0)
        self._update_attribute(self.attributes_by_name["max_present_value"].id, 950)
        self._update_attribute(self.attributes_by_name["resolution"].id, 10)
        self._update_attribute(
            self.attributes_by_name["engineering_units"].id, 118
        )  # 31: meters

class TuyaMmwRadarMaxRange(TuyaAttributesCluster, AnalogOutput):
    """AnalogOutput cluster for max range."""

    def __init__(self, *args, **kwargs):
        """Init."""
        super().__init__(*args, **kwargs)
        self._update_attribute(
            self.attributes_by_name["description"].id, "Max range"
        )
        self._update_attribute(self.attributes_by_name["min_present_value"].id, 0)
        self._update_attribute(self.attributes_by_name["max_present_value"].id, 950)
        self._update_attribute(self.attributes_by_name["resolution"].id, 10)
        self._update_attribute(
            self.attributes_by_name["engineering_units"].id, 118
        )  # 31: meters

class TuyaMmwRadarDetectionDelay(TuyaAttributesCluster, AnalogOutput):
    """AnalogOutput cluster for detection delay."""

    def __init__(self, *args, **kwargs):
        """Init."""
        super().__init__(*args, **kwargs)
        self._update_attribute(
            self.attributes_by_name["description"].id, "Detection delay"
        )
        self._update_attribute(self.attributes_by_name["min_present_value"].id, 000)
        self._update_attribute(self.attributes_by_name["max_present_value"].id, 20000)
        self._update_attribute(self.attributes_by_name["resolution"].id, 100)
        self._update_attribute(
            self.attributes_by_name["engineering_units"].id, 159
        )  # 73: seconds

class TuyaMmwRadarFadingTime(TuyaAttributesCluster, AnalogOutput):
    """AnalogOutput cluster for fading time."""

    def __init__(self, *args, **kwargs):
        """Init."""
        super().__init__(*args, **kwargs)
        self._update_attribute(
            self.attributes_by_name["description"].id, "Fading time"
        )
        self._update_attribute(self.attributes_by_name["min_present_value"].id, 0000)
        self._update_attribute(self.attributes_by_name["max_present_value"].id, 200000)
        self._update_attribute(self.attributes_by_name["resolution"].id, 1000)
        self._update_attribute(
            self.attributes_by_name["engineering_units"].id, 159
        )  # 73: seconds

class TuyaMmwRadarTargetDistance(TuyaAttributesCluster, AnalogInput):
    """AnalogInput cluster for target distance."""

    def __init__(self, *args, **kwargs):
        """Init."""
        super().__init__(*args, **kwargs)
        self._update_attribute(
            self.attributes_by_name["description"].id, "Target distance"
        )
        self._update_attribute(
            self.attributes_by_name["engineering_units"].id, 31
        )  # 31: meters



class TuyaMmwRadarCluster(NoManufacturerCluster, TuyaMCUCluster):
    """Mmw radar cluster."""
    attributes = TuyaMCUCluster.attributes.copy()
    attributes.update(
        {
            # ramdom attribute IDs
            0xEF01: ("occupancy", t.uint32_t, True),
            0xEF02: ("sensitivity", t.uint32_t, True),
            0xEF03: ("min_range", t.uint32_t, True),
            0xEF04: ("max_range", t.uint32_t, True),
            0xEF06: ("self_test", TuyaMmwRadarSelfTest, True),
            0xEF09: ("target_distance", t.uint32_t, True),
            0xEF65: ("detection_delay", t.uint32_t, True),
            0xEF66: ("fading_time", t.uint32_t, True),
            0xEF67: ("cli", t.CharacterString, True),
            0xEF68: ("illuminance", t.uint32_t, True),
        }
    )

    dp_to_attribute: Dict[int, DPToAttributeMapping] = {
        1: DPToAttributeMapping(
            TuyaOccupancySensing.ep_attribute,
            "occupancy",
            # dp_type=TuyaDPType.BOOL,
        ),
        2: DPToAttributeMapping(
            TuyaMmwRadarSensitivity.ep_attribute,
            "present_value",
            # dp_type=TuyaDPType.VALUE,
        ),
        3: DPToAttributeMapping(
            TuyaMmwRadarMinRange.ep_attribute,
            "present_value",
            # dp_type=TuyaDPType.VALUE,
            endpoint_id=2,
            #converter=lambda x: x / 100,
            #dp_converter=lambda x: x * 100,
        ),
        4: DPToAttributeMapping(
            TuyaMmwRadarMaxRange.ep_attribute,
            "present_value",
            # dp_type=TuyaDPType.VALUE,
            endpoint_id=3,
            #converter=lambda x: x / 100,
            #dp_converter=lambda x: x * 100,
        ),
        6: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "self_test",
            # dp_type=TuyaDPType.ENUM,
        ),
        9: DPToAttributeMapping(
            TuyaMmwRadarTargetDistance.ep_attribute,
            "present_value",
            #converter=lambda x: x / 100,
            # dp_type=TuyaDPType.VALUE,
        ),
        101: DPToAttributeMapping(
            TuyaMmwRadarDetectionDelay.ep_attribute,
            "present_value",
            # dp_type=TuyaDPType.VALUE,
            converter=lambda x: x * 100,
            dp_converter=lambda x: x // 100,
            endpoint_id=4,
        ),
        102: DPToAttributeMapping(
            TuyaMmwRadarFadingTime.ep_attribute,
            "present_value",
            # dp_type=TuyaDPType.VALUE,
            converter=lambda x: x * 100,
            dp_converter=lambda x: x // 100,
            endpoint_id=5,
        ),
        103: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "cli",
            # dp_type=TuyaDPType.STRING,
        ),
        104: DPToAttributeMapping(
            TuyaIlluminanceMeasurement.ep_attribute,
            "measured_value",
            # dp_type=TuyaDPType.VALUE,
            converter=lambda x: 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",
        6: "_dp_2_attr_update",
        9: "_dp_2_attr_update",
        101: "_dp_2_attr_update",
        102: "_dp_2_attr_update",
        103: "_dp_2_attr_update",
        104: "_dp_2_attr_update",
    }

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

    signature = {
        #  endpoint=1, profile=260, device_type=81, device_version=1,
        #  input_clusters=[0, 4, 5, 61184], output_clusters=[25, 10]
        "models_info": [
            ("_TZE200_ar0slwnd", "TS0601"),
            ("_TZE200_sfiy5tfs", "TS0601"),
            ("_TZE200_mrf6vtua", "TS0601"),
            ("_TZE200_ztc6ggyl", "TS0601"),
            ("_TZE204_ztc6ggyl", "TS0601"),
        ],
        ENDPOINTS: {
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    TuyaNewManufCluster.cluster_id,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
        },
    }


    replacement = {
        ENDPOINTS: {
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.OCCUPANCY_SENSOR,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    TuyaMmwRadarCluster,
                    TuyaIlluminanceMeasurement,
                    TuyaOccupancySensing,
                    TuyaMmwRadarTargetDistance,
                    TuyaMmwRadarSensitivity,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            2: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
                INPUT_CLUSTERS: [
                    TuyaMmwRadarMinRange,
                ],
                OUTPUT_CLUSTERS: [],
            },
            3: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
                INPUT_CLUSTERS: [
                    TuyaMmwRadarMaxRange,
                ],
                OUTPUT_CLUSTERS: [],
            },
            4: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
                INPUT_CLUSTERS: [
                    TuyaMmwRadarDetectionDelay,
                ],
                OUTPUT_CLUSTERS: [],
            },
            5: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
                INPUT_CLUSTERS: [
                    TuyaMmwRadarFadingTime,
                ],
                OUTPUT_CLUSTERS: [],
            },
        }
    }

@broyuken
Copy link

broyuken commented Mar 2, 2023

thank you, you need to remove those tick marks on the very last line, but other than that it seems to work great!

edit: actually, the illuminance sensor doesn't seem to show up with this quirk

@buzzeddesign
Copy link

Edited to remove the Markdown ticks that were in an unfortunate spot 😁

@broyuken
Copy link

broyuken commented Mar 2, 2023

I've rebooted after installing the quirk and removed/re-added the device a couple times now and it still doesn't show. the occupancy and controls for that all show up, but nothing about illuminance

@buzzeddesign
Copy link

Got it - it was this line: converter=lambda x: math.log10(x) * 10000 + 1 if x > 0 else 0,

Those returned numbers need to be int to be interpreted as dp_type of VALUE (see here) - so fixing like this fixes the illuminance sensor: converter=lambda x: int(math.log10(x) * 10000 + 1) if x > 0 else int(0),

Edited comment above to fix it

@PeterKawa
Copy link

"I commented out all of the references to TuyaDPType and it began working as it did before"
That was the solution for me as well!

@TheJulianJES
Copy link
Collaborator

A solution would be to merge the "custom quirk" into zha-quirks, so that custom quirks don't have to be used.
Is there an open issue about the device you're using?
(If so, could you link that? And if not, would one of you please open an issue and provide the information + working custom quirk there?)

@broyuken
Copy link

broyuken commented Mar 3, 2023

I have your updated quirk, but am still only getting occupancy sensor, no illuminance

@MSe-5-14
Copy link

MSe-5-14 commented Mar 3, 2023

I'm missing the 'Target Distance' sensor. I can see the information when i check the TuyaMmwRadarTargetDistance cluster. is there supposed to be a sensor or not?
image

@broyuken
Copy link

broyuken commented Mar 4, 2023

Anything else we can try to get these entities back?

@TheJulianJES
Copy link
Collaborator

TheJulianJES commented Mar 4, 2023

A solution would be to merge the "custom quirk" into zha-quirks, so that custom quirks don't have to be used.
Is there an open issue about the device you're using?
(If so, could you link that? And if not, would one of you please open an issue and provide the information + working custom quirk there?)

@broyuken
Let's not clutter this PR anymore. Not sure what exact device you're using, but please create a new issue for it if there isn't one. Provide the custom quirk, device signature, ... and let's continue the discussion there. (But link the issue here for everyone else to see)

@broyuken
Copy link

broyuken commented Mar 4, 2023

its the one in this issue. And the quirk was working perfectly until 2023.3 and the tuyaDP issue. Thats why I was bringing it back up here. But sure, I'll create a new issue

@broyuken
Copy link

broyuken commented Mar 4, 2023

ok strangely enough I just re-added it for about the 10th time after about the same HA reboots and now the sensor is there, who knows...I did update to 2023.3.1 this morning but outside of that I didn't do anything else different. Regardless, it's there now, I'm happy.

@TheJulianJES
Copy link
Collaborator

@broyuken

its the one in this issue

This is a PR and not an issue. That's the reason I was asking to move this to an issue.
Custom quirks WILL BREAK again. It's not a matter of if, but when.

So in order to finally get this device properly supported without any custom quirks, can you:

  1. Create an issue (device support request): https://github.com/zigpy/zha-device-handlers/issues/new/choose
  2. Provide the device signature (device config page -> three dots -> Manage Zigbee device -> Signature)
  3. Provide the diagnostic information (device config page -> three dots -> Download diagnostics)
  4. Provide the custom quirk (or a download link to it) (all in that new issue)
  5. Mention what sensor was missing for you (and only eventually showed up after multiple HA restarts)

That way, we can properly track this and finally support it correctly. Again, your custom quirk will break again. It's not a solution.

@antiguangenius
Copy link

i cant get the sensors to show, it was fine before 2023.3, how can I fix??

Same error as well - believe this is due to this PR in the zigpy project (TuyaDPType is now automatic?)

I commented out all of the references to TuyaDPType and it began working as it did before:


import math
from typing import Dict, Optional, Tuple, Union

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

from zhaquirks import Bus, LocalDataCluster, MotionOnEvent
from zhaquirks.const import (
    DEVICE_TYPE,
    ENDPOINTS,
    INPUT_CLUSTERS,
    MODEL,
    MOTION_EVENT,
    OUTPUT_CLUSTERS,
    PROFILE_ID,
)

from zhaquirks.tuya import (
    NoManufacturerCluster,
    TuyaLocalCluster,
    TuyaNewManufCluster,
)
from zhaquirks.tuya.mcu import (
    # TuyaDPType,
    DPToAttributeMapping,
    TuyaAttributesCluster,
    TuyaMCUCluster,
)


class TuyaMmwRadarSelfTest(t.enum8):
    """Mmw radar self test values."""
    TESTING = 0
    TEST_SUCCESS = 1
    TEST_FAILURE = 2
    OTHER = 3
    COMM_FAULT = 4
    RADAR_FAULT = 5

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

class TuyaIlluminanceMeasurement(IlluminanceMeasurement, TuyaLocalCluster):
    """Tuya local IlluminanceMeasurement cluster."""

class TuyaMmwRadarSensitivity(TuyaAttributesCluster, AnalogOutput):
    """AnalogOutput cluster for sensitivity."""

    def __init__(self, *args, **kwargs):
        """Init."""
        super().__init__(*args, **kwargs)
        self._update_attribute(
            self.attributes_by_name["description"].id, "Sensitivity"
        )
        self._update_attribute(self.attributes_by_name["min_present_value"].id, 1)
        self._update_attribute(self.attributes_by_name["max_present_value"].id, 9)
        self._update_attribute(self.attributes_by_name["resolution"].id, 1)

class TuyaMmwRadarMinRange(TuyaAttributesCluster, AnalogOutput):
    """AnalogOutput cluster for min range."""

    def __init__(self, *args, **kwargs):
        """Init."""
        super().__init__(*args, **kwargs)
        self._update_attribute(
            self.attributes_by_name["description"].id, "Min range"
        )
        self._update_attribute(self.attributes_by_name["min_present_value"].id, 0)
        self._update_attribute(self.attributes_by_name["max_present_value"].id, 950)
        self._update_attribute(self.attributes_by_name["resolution"].id, 10)
        self._update_attribute(
            self.attributes_by_name["engineering_units"].id, 118
        )  # 31: meters

class TuyaMmwRadarMaxRange(TuyaAttributesCluster, AnalogOutput):
    """AnalogOutput cluster for max range."""

    def __init__(self, *args, **kwargs):
        """Init."""
        super().__init__(*args, **kwargs)
        self._update_attribute(
            self.attributes_by_name["description"].id, "Max range"
        )
        self._update_attribute(self.attributes_by_name["min_present_value"].id, 0)
        self._update_attribute(self.attributes_by_name["max_present_value"].id, 950)
        self._update_attribute(self.attributes_by_name["resolution"].id, 10)
        self._update_attribute(
            self.attributes_by_name["engineering_units"].id, 118
        )  # 31: meters

class TuyaMmwRadarDetectionDelay(TuyaAttributesCluster, AnalogOutput):
    """AnalogOutput cluster for detection delay."""

    def __init__(self, *args, **kwargs):
        """Init."""
        super().__init__(*args, **kwargs)
        self._update_attribute(
            self.attributes_by_name["description"].id, "Detection delay"
        )
        self._update_attribute(self.attributes_by_name["min_present_value"].id, 000)
        self._update_attribute(self.attributes_by_name["max_present_value"].id, 20000)
        self._update_attribute(self.attributes_by_name["resolution"].id, 100)
        self._update_attribute(
            self.attributes_by_name["engineering_units"].id, 159
        )  # 73: seconds

class TuyaMmwRadarFadingTime(TuyaAttributesCluster, AnalogOutput):
    """AnalogOutput cluster for fading time."""

    def __init__(self, *args, **kwargs):
        """Init."""
        super().__init__(*args, **kwargs)
        self._update_attribute(
            self.attributes_by_name["description"].id, "Fading time"
        )
        self._update_attribute(self.attributes_by_name["min_present_value"].id, 0000)
        self._update_attribute(self.attributes_by_name["max_present_value"].id, 200000)
        self._update_attribute(self.attributes_by_name["resolution"].id, 1000)
        self._update_attribute(
            self.attributes_by_name["engineering_units"].id, 159
        )  # 73: seconds

class TuyaMmwRadarTargetDistance(TuyaAttributesCluster, AnalogInput):
    """AnalogInput cluster for target distance."""

    def __init__(self, *args, **kwargs):
        """Init."""
        super().__init__(*args, **kwargs)
        self._update_attribute(
            self.attributes_by_name["description"].id, "Target distance"
        )
        self._update_attribute(
            self.attributes_by_name["engineering_units"].id, 31
        )  # 31: meters



class TuyaMmwRadarCluster(NoManufacturerCluster, TuyaMCUCluster):
    """Mmw radar cluster."""
    attributes = TuyaMCUCluster.attributes.copy()
    attributes.update(
        {
            # ramdom attribute IDs
            0xEF01: ("occupancy", t.uint32_t, True),
            0xEF02: ("sensitivity", t.uint32_t, True),
            0xEF03: ("min_range", t.uint32_t, True),
            0xEF04: ("max_range", t.uint32_t, True),
            0xEF06: ("self_test", TuyaMmwRadarSelfTest, True),
            0xEF09: ("target_distance", t.uint32_t, True),
            0xEF65: ("detection_delay", t.uint32_t, True),
            0xEF66: ("fading_time", t.uint32_t, True),
            0xEF67: ("cli", t.CharacterString, True),
            0xEF68: ("illuminance", t.uint32_t, True),
        }
    )

    dp_to_attribute: Dict[int, DPToAttributeMapping] = {
        1: DPToAttributeMapping(
            TuyaOccupancySensing.ep_attribute,
            "occupancy",
            # dp_type=TuyaDPType.BOOL,
        ),
        2: DPToAttributeMapping(
            TuyaMmwRadarSensitivity.ep_attribute,
            "present_value",
            # dp_type=TuyaDPType.VALUE,
        ),
        3: DPToAttributeMapping(
            TuyaMmwRadarMinRange.ep_attribute,
            "present_value",
            # dp_type=TuyaDPType.VALUE,
            endpoint_id=2,
            #converter=lambda x: x / 100,
            #dp_converter=lambda x: x * 100,
        ),
        4: DPToAttributeMapping(
            TuyaMmwRadarMaxRange.ep_attribute,
            "present_value",
            # dp_type=TuyaDPType.VALUE,
            endpoint_id=3,
            #converter=lambda x: x / 100,
            #dp_converter=lambda x: x * 100,
        ),
        6: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "self_test",
            # dp_type=TuyaDPType.ENUM,
        ),
        9: DPToAttributeMapping(
            TuyaMmwRadarTargetDistance.ep_attribute,
            "present_value",
            #converter=lambda x: x / 100,
            # dp_type=TuyaDPType.VALUE,
        ),
        101: DPToAttributeMapping(
            TuyaMmwRadarDetectionDelay.ep_attribute,
            "present_value",
            # dp_type=TuyaDPType.VALUE,
            converter=lambda x: x * 100,
            dp_converter=lambda x: x // 100,
            endpoint_id=4,
        ),
        102: DPToAttributeMapping(
            TuyaMmwRadarFadingTime.ep_attribute,
            "present_value",
            # dp_type=TuyaDPType.VALUE,
            converter=lambda x: x * 100,
            dp_converter=lambda x: x // 100,
            endpoint_id=5,
        ),
        103: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "cli",
            # dp_type=TuyaDPType.STRING,
        ),
        104: DPToAttributeMapping(
            TuyaIlluminanceMeasurement.ep_attribute,
            "measured_value",
            # dp_type=TuyaDPType.VALUE,
            converter=lambda x: 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",
        6: "_dp_2_attr_update",
        9: "_dp_2_attr_update",
        101: "_dp_2_attr_update",
        102: "_dp_2_attr_update",
        103: "_dp_2_attr_update",
        104: "_dp_2_attr_update",
    }

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

    signature = {
        #  endpoint=1, profile=260, device_type=81, device_version=1,
        #  input_clusters=[0, 4, 5, 61184], output_clusters=[25, 10]
        "models_info": [
            ("_TZE200_ar0slwnd", "TS0601"),
            ("_TZE200_sfiy5tfs", "TS0601"),
            ("_TZE200_mrf6vtua", "TS0601"),
            ("_TZE200_ztc6ggyl", "TS0601"),
            ("_TZE204_ztc6ggyl", "TS0601"),
        ],
        ENDPOINTS: {
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    TuyaNewManufCluster.cluster_id,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
        },
    }


    replacement = {
        ENDPOINTS: {
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.OCCUPANCY_SENSOR,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    TuyaMmwRadarCluster,
                    TuyaIlluminanceMeasurement,
                    TuyaOccupancySensing,
                    TuyaMmwRadarTargetDistance,
                    TuyaMmwRadarSensitivity,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            2: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
                INPUT_CLUSTERS: [
                    TuyaMmwRadarMinRange,
                ],
                OUTPUT_CLUSTERS: [],
            },
            3: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
                INPUT_CLUSTERS: [
                    TuyaMmwRadarMaxRange,
                ],
                OUTPUT_CLUSTERS: [],
            },
            4: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
                INPUT_CLUSTERS: [
                    TuyaMmwRadarDetectionDelay,
                ],
                OUTPUT_CLUSTERS: [],
            },
            5: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
                INPUT_CLUSTERS: [
                    TuyaMmwRadarFadingTime,
                ],
                OUTPUT_CLUSTERS: [],
            },
        }
    }

i cant get the sensors to show, it was fine before 2023.3, how can I fix??
i removed and add sensor many times....

@TheJulianJES
Copy link
Collaborator

Like mentioned before, create a new issue if you still have problems. Those sensors might not show up until movement was triggered and HA is restarted, because the attributes are not properly initialized.
So please just create a new issue for that (and other issues), so they can be tracked and fixed in the future.

@zigpy zigpy locked and limited conversation to collaborators Mar 6, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.