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
725 stars 672 forks source link

[Device Support Request]MOES 2Gang inwall switch #1038

Closed Barmatuhin closed 2 years ago

Barmatuhin commented 3 years ago

MOES 2gang in-wall switch have NO entities after pairing. image

Describe the solution you'd like Add support for TS0601 _TZE200_g1ib5ldv for first and second gang.

Device signature - this can be acquired by removing the device from ZHA and pairing it again from the add devices screen. Be sure to add the entire content of the log panel after pairing the device to a code block below this line. { "node_descriptor": "NodeDescriptor(logical_type=<LogicalType.EndDevice: 2>, complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=<FrequencyBand.Freq2400MHz: 8>, mac_capability_flags=<MACCapabilityFlags.AllocateAddress: 128>, manufacturer_code=4098, maximum_buffer_size=82, maximum_incoming_transfer_size=82, server_mask=11264, maximum_outgoing_transfer_size=82, descriptor_capability_field=<DescriptorCapability.NONE: 0>, allocate_address=True, is_alternate_pan_coordinator=False, is_coordinator=False, is_end_device=True, is_full_function_device=False, is_mains_powered=False, is_receiver_on_when_idle=False, is_router=False, *is_security_capable=False)", "endpoints": { "1": { "profile_id": 260, "device_type": "0x0051", "in_clusters": [ "0x0000", "0x0004", "0x0005", "0xef00" ], "out_clusters": [ "0x000a", "0x0019" ] } }, "manufacturer": "_TZE200_g1ib5ldv", "model": "TS0601", "class": "zigpy.device.Device" }

Additional context image

MattWestb commented 2 years ago

Its main powered but have not the flag sett for it. Do you getting zha_event from the development tools then pressing the gangs ?

MattWestb commented 2 years ago

Sorry i was not seen that is one TS0601 device and needing one tuya quirk for working at all :-((

Barmatuhin commented 2 years ago

Sorry i was not seen that is one TS0601 device and needing one tuya quirk for working at all :-((

Exactly. And if i add manually _TZE200_g1ib5ldv to ts0601 tuya quirk, it doesn't recognize second button (only 1 EP in custom quirks for TS0601 devices). Similar request here - https://github.com/zigpy/zha-device-handlers/issues/972 and here - https://github.com/zigpy/zha-device-handlers/issues/1033 A lot of tuya devices with 2 gans need quirk modification to support second EndPoint.... hope dev's hear us

MattWestb commented 2 years ago

Great that you was getting the quirk loading and getting the first EP working !! Its need little deeper code fixing for getting the multi gangs switches and dimmers to working OK in ZHA and some with the right knowledge need taking one look on it and implanting one fix them.

If you have the first EP working you can doing one PR with the change and writing one comment that only the first is working OK then its better then no support at all for our users but not very good but we cant do so much for the moment.

Barmatuhin commented 2 years ago

Any change to get support for this type of devices (two endpoint main powered devices)? Also this request - https://github.com/zigpy/zha-device-handlers/issues/1033

Barmatuhin commented 2 years ago

Please, add support for this device. Looks it's like end device and if i use HASSOS with RPI i can't edit build-in quirks, so can't get any gang working.

MattWestb commented 2 years ago

Exactly. And if i add manually _TZE200_g1ib5ldv to ts0601 tuya quirk, it doesn't recognize second button (only 1 EP in custom quirks for TS0601 devices)

So HASSOS is not possible edit the build in quirks ?!!?

693

The best you can do sis adding it as one switch in ts0601_singleswitch.py and then enabling debug for quirk by adding:

    zhaquirks: debug

in your HA config so you can getting the DP the device is sending. Without any logs its not possible doing any thing.

javicalle commented 2 years ago

Hi there, I have a quick and dirty functional version. I don't think it's a good example or a version to consider, but in case it might be useful to someone, I'll leave it here.

class TuyaOnOff_Base(CustomCluster, OnOff):
    """Tuya On/Off base cluster for multibutton On/Off device."""

    _common_endpoint = None

    @classmethod
    def set_endpoint(cls, endpoint):
        """Set the conductor endpoint to use."""
        cls._common_endpoint = endpoint

    @classmethod
    def get_endpoint(cls):
        """Get the conductor endpoint."""
        return cls._common_endpoint

    def __init__(self, *args, **kwargs):
        """Init."""
        super().__init__(*args, **kwargs)
        self.endpoint.device.switch_bus.add_listener(self)

    def switch_event(self, channel, state):
        """Switch event."""
        _LOGGER.debug(
            "%s - Received switch event message --> channel: %d, state: %d",
            self.endpoint.device.ieee,
            channel,
            state,
        )
        # update status only if event == endpoint
        if self.endpoint.endpoint_id == channel:
            self._update_attribute(ATTR_ON_OFF, state)

    def command(
        self,
        command_id: Union[foundation.Command, int, t.uint8_t],
        *args,
        manufacturer: Optional[Union[int, t.uint16_t]] = None,
        expect_reply: bool = True,
        tsn: Optional[Union[int, t.uint8_t]] = None,
    ):
        """Override the default Cluster command."""

        _LOGGER.debug(
            "%s - command --> id: %d, endpoint: %d",
            self.endpoint.device.ieee,
            command_id,
            self.endpoint.endpoint_id,
        )

        if command_id in (0x0000, 0x0001):
            cmd_payload = TuyaManufCluster.Command()
            cmd_payload.status = 0
            cmd_payload.tsn = 0
            cmd_payload.command_id = TUYA_CMD_BASE + self.endpoint.endpoint_id
            cmd_payload.function = 0
            cmd_payload.data = [1, command_id]

            return TuyaOnOff_Base.get_endpoint().tuya_manufacturer.command(
                TUYA_SET_DATA, cmd_payload, expect_reply=True
            )

        return foundation.Status.UNSUP_CLUSTER_COMMAND

class TuyaOnOff_conductor(TuyaOnOff_Base):
    """Tuya OnOff conductor button."""

    name = "TuyaOnOff_conductor"

    def __init__(self, *args, **kwargs):
        """Init."""
        super().__init__(*args, **kwargs)
        TuyaOnOff_Base.set_endpoint(self.endpoint)

class TuyaOnOff_follower(TuyaOnOff_Base):
    """Tuya OnOff fake buttons."""

    name = "TuyaOnOff_follower"

The TuyaOnOff_Base class is practically the TuyaOnOff class with the particularity of the singleton to store the 'real' endpoint. Also, there is a event filter at method switch_event and command uses the singleton to comunicate with device.

Then, in the switch definition a fake endpoint for each button (apart from the button 1) must be created. The endpoint_id must chase with the event channel:

class TuyaDoubleSwitch(TuyaSwitch):
    """Tuya double switch device."""

    signature = {
        # "node_descriptor": "<NodeDescriptor byte1=1 byte2=64 mac_capability_flags=142 manufacturer_code=4098
        #                       maximum_buffer_size=82 maximum_incoming_transfer_size=82 server_mask=11264
        #                       maximum_outgoing_transfer_size=82 descriptor_capability_field=0>",
        # device_version=1
        # input_clusters=[0x0000, 0x0004, 0x0005, 0xef00]
        # output_clusters=[0x000a, 0x0019]
        # <SimpleDescriptor endpoint=1 profile=260 device_type=51 device_version=1 input_clusters=[0, 4, 5, 61184] output_clusters=[10, 25]>
        MODELS_INFO: [
            ("_TZE200_g1ib5ldv", "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,
                    TuyaManufCluster.cluster_id,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            }
        },
    }

    replacement = {
        ENDPOINTS: {
            1: {
                DEVICE_TYPE: zha.DeviceType.ON_OFF_LIGHT,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    TuyaManufacturerClusterOnOff,
                    TuyaOnOff_conductor,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            2: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.ON_OFF_LIGHT,
                INPUT_CLUSTERS: [
                    TuyaOnOff_follower,
                ],
                OUTPUT_CLUSTERS: [],
            }
        }
    }

This implementation is functional, with some limitations. When button 2 is activated from the wall switch, there is a delay until the state is refreshed in HA. This is because the device sends the frame of button 1 multiple times before sending the frame of button 2:

2021-11-02 03:13:06 DEBUG (MainThread) [ts0601] 5c:02:72:ff:fe:1a:11:75 - Received switch event message --> channel: 1, state: 1
2021-11-02 03:13:06 DEBUG (MainThread) [ts0601] 5c:02:72:ff:fe:1a:11:75 - Received switch event message --> channel: 1, state: 1
2021-11-02 03:13:06 DEBUG (MainThread) [ts0601] 5c:02:72:ff:fe:1a:11:75 - Received switch event message --> channel: 1, state: 1
2021-11-02 03:13:06 DEBUG (MainThread) [ts0601] 5c:02:72:ff:fe:1a:11:75 - Received switch event message --> channel: 1, state: 1
2021-11-02 03:13:06 DEBUG (MainThread) [ts0601] 5c:02:72:ff:fe:1a:11:75 - Received switch event message --> channel: 1, state: 1
2021-11-02 03:13:06 DEBUG (MainThread) [ts0601] 5c:02:72:ff:fe:1a:11:75 - Received switch event message --> channel: 1, state: 1
2021-11-02 03:13:07 DEBUG (MainThread) [ts0601] 5c:02:72:ff:fe:1a:11:75 - Received switch event message --> channel: 1, state: 1
2021-11-02 03:13:07 DEBUG (MainThread) [ts0601] 5c:02:72:ff:fe:1a:11:75 - Received switch event message --> channel: 1, state: 1
2021-11-02 03:13:07 DEBUG (MainThread) [ts0601] 5c:02:72:ff:fe:1a:11:75 - Received switch event message --> channel: 1, state: 1
2021-11-02 03:13:07 DEBUG (MainThread) [ts0601] 5c:02:72:ff:fe:1a:11:75 - Received switch event message --> channel: 1, state: 1
2021-11-02 03:13:08 DEBUG (MainThread) [ts0601] 5c:02:72:ff:fe:1a:11:75 - Received switch event message --> channel: 1, state: 1
2021-11-02 03:13:08 DEBUG (MainThread) [ts0601] 5c:02:72:ff:fe:1a:11:75 - Received switch event message --> channel: 1, state: 1
2021-11-02 03:13:08 DEBUG (MainThread) [ts0601] 5c:02:72:ff:fe:1a:11:75 - Received switch event message --> channel: 1, state: 1
2021-11-02 03:13:08 DEBUG (MainThread) [ts0601] 5c:02:72:ff:fe:1a:11:75 - Received switch event message --> channel: 1, state: 1
2021-11-02 03:13:09 DEBUG (MainThread) [ts0601] 5c:02:72:ff:fe:1a:11:75 - Received switch event message --> channel: 1, state: 1
2021-11-02 03:13:09 DEBUG (MainThread) [ts0601] 5c:02:72:ff:fe:1a:11:75 - Received switch event message --> channel: 1, state: 1
2021-11-02 03:13:09 DEBUG (MainThread) [ts0601] 5c:02:72:ff:fe:1a:11:75 - Received switch event message --> channel: 1, state: 1
2021-11-02 03:13:09 DEBUG (MainThread) [ts0601] 5c:02:72:ff:fe:1a:11:75 - Received switch event message --> channel: 1, state: 1
2021-11-02 03:13:10 DEBUG (MainThread) [ts0601] 5c:02:72:ff:fe:1a:11:75 - Received switch event message --> channel: 1, state: 1
2021-11-02 03:13:10 DEBUG (MainThread) [ts0601] 5c:02:72:ff:fe:1a:11:75 - Received switch event message --> channel: 1, state: 1
2021-11-02 03:13:10 DEBUG (MainThread) [ts0601] 5c:02:72:ff:fe:1a:11:75 - Received switch event message --> channel: 1, state: 1
2021-11-02 03:13:10 DEBUG (MainThread) [ts0601] 5c:02:72:ff:fe:1a:11:75 - Received switch event message --> channel: 1, state: 1
2021-11-02 03:13:11 DEBUG (MainThread) [ts0601] 5c:02:72:ff:fe:1a:11:75 - Received switch event message --> channel: 1, state: 1
2021-11-02 03:13:11 DEBUG (MainThread) [ts0601] 5c:02:72:ff:fe:1a:11:75 - Received switch event message --> channel: 1, state: 1
2021-11-02 03:13:11 DEBUG (MainThread) [ts0601] 5c:02:72:ff:fe:1a:11:75 - Received switch event message --> channel: 1, state: 1
2021-11-02 03:13:11 DEBUG (MainThread) [ts0601] 5c:02:72:ff:fe:1a:11:75 - Received switch event message --> channel: 1, state: 1
2021-11-02 03:13:12 DEBUG (MainThread) [ts0601] 5c:02:72:ff:fe:1a:11:75 - Received switch event message --> channel: 2, state: 0
2021-11-02 03:13:12 DEBUG (MainThread) [ts0601] 5c:02:72:ff:fe:1a:11:75 - Received switch event message --> channel: 2, state: 0
2021-11-02 03:13:13 DEBUG (MainThread) [ts0601] 5c:02:72:ff:fe:1a:11:75 - Received switch event message --> channel: 782, state: 0
2021-11-02 03:13:13 DEBUG (MainThread) [ts0601] 5c:02:72:ff:fe:1a:11:75 - Received switch event message --> channel: 782, state: 0
2021-11-02 03:13:14 DEBUG (MainThread) [ts0601] 5c:02:72:ff:fe:1a:11:75 - Received switch event message --> channel: 783, state: 2
2021-11-02 03:13:14 DEBUG (MainThread) [ts0601] 5c:02:72:ff:fe:1a:11:75 - Received switch event message --> channel: 783, state: 2

For your consideration:

After struggling with the code I am quite clear that this is not the way to go.

I don't know if it would be possible to define N switches on endpoint 1, but I think it would make more sense for it to be that way (actually the device only publishes 1 endpoint). See if I can find some time to try this other approach.

Barmatuhin commented 2 years ago

The best you can do sis adding it as one switch in ts0601_singleswitch.py and then enabling debug for quirk by adding:

Thanks a lot for helping with editing in container. Finally now i know how to do it! So i open console of homeassistant container and add one string into MODELS_INFO, restart HA from server control, remove my switch from list, pair it again but it's appears as previous, without quirk

🍋 Suplemon Editor v0.2.1 - ⌚22:34 02.11. [ts0601_singleswitch.py] 24 signature = { 25 # "node_descriptor": "<NodeDescriptor byte1=1 byte2=64 mac_capability_flags=142 manufacturer_code 26 # maximum_buffer_size=82 maximum_incoming_transfer_size=82 server_mask=1126 27 # maximum_outgoing_transfer_size=82 descriptor_capability_field=0>", 28 # device_version=1 29 # input_clusters=[0x0000,0x0004, 0x0005,0x000a, 0xef00] 30 # output_clusters=[0x0019] 31 # <SimpleDescriptor endpoint=1 profile=260 device_type=81 device_version=1 input_clusters=[0, 4, 32 MODELS_INFO: [("_TZE200_7tdtqgwv", "TS0601"), 33 ("_TZE200_g1ib5ldv", "TS0601"), 34 ], 35 ENDPOINTS: { 36 1: { 37 PROFILE_ID: zha.PROFILE_ID, 38 DEVICE_TYPE: zha.DeviceType.SMART_PLUG, 39 INPUT_CLUSTERS: [ 40 Basic.cluster_id, 41 Groups.cluster_id, 42 Scenes.cluster_id, 43 Time.cluster_id, 44 TuyaManufCluster.cluster_id, 45 ], 46 OUTPUT_CLUSTERS: [Ota.cluster_id], 47 } 48 }, 49 } ^S Save F1 Save as F2 Reload F5 Undo F6 Redo ^O Open ^C Copy ^X Cut ^V Paste ^F Find ^D Find next ^A Find all ^K Duplicate line ESC Single cursor ^G Go to ^E Run command F8 Mouse mode ^H Help ^Q Exit host:a0d7b954-ssh.local.hass.io @0,48 cur:1 buf:1

And this i found in Logs

2021-11-02 22:54:15 WARNING (MainThread) [zigpy.zcl] [0x7a4f:1:0xef00] Unknown cluster-specific command 17 2021-11-02 22:54:15 DEBUG (MainThread) [zigpy.zcl] [0x7a4f:1:0xef00] ZCL request 0x0011: b'\x00\n@' 2021-11-02 22:54:15 DEBUG (MainThread) [zigpy.zcl] [0x7a4f:1:0xef00] No handler for cluster command 17 2021-11-02 22:54:15 DEBUG (MainThread) [zigpy_deconz.api] 'aps_data_indication' response from , ep: 1, profile: 0x0104, cluster_id: 0xef00, data: b'091311000a40' 2021-11-02 22:54:15 DEBUG (MainThread) [zigpy_deconz.api] Device state changed response: [<DeviceState.128|APSDE_DATA_REQUEST_SLOTS_AVAILABLE|APSDE_DATA_INDICATION|2: 170>, 0] 2021-11-02 22:54:15 DEBUG (MainThread) [zigpy_deconz.api] Command Command.aps_data_indication (1, 1) 2021-11-02 22:54:15 DEBUG (MainThread) [zigpy_deconz.api] APS data indication response: [29, <DeviceState.APSDE_DATA_REQUEST_SLOTS_AVAILABLE|2: 34>, , 1, , 1, 260, 61184, b'\t\x13\x11\x00\n@', 0, 175, 255, 7, 255, 0, 0, -62] 2021-11-02 22:54:15 DEBUG (MainThread) [zigpy.zcl] [0x7a4f:1:0xef00] ZCL deserialize: <ZCLHeader frame_control= manufacturer=None tsn=19 command_id=17>

MattWestb commented 2 years ago

@Barmatuhin You need putting your device ID in one signature that is the same as your device (all cluster must match or ZHA is not loading the quirk) and if you dont find one you making one new for your device. But i think its better testing the new one that @javicalle have made and see if its working for you (it have the same device ID but must being out together for working.

@javicalle Great work !!!

The problem that the device is resending the same command is because all communication is on the tuya cluster is not standard and if not getting any default response (send_default_rsp) from the system then the device is thinking the command sent its lost and sending the same frame with the same tsn (transaction number) and the continuing that until its timing out and start trying sending new commands.

"Normal" device is using ZCL (Zigbee CLuster Labery) is the Zigbee stack doing that but the tuya cluster is out off bound and must being done of the application. Some code in the INIT have it but its looks like it not being used the you is receiving command from the switch.

If you is having little debug logging you can see all command received and sent and there tsn like in this is thetsn=19:

2021-11-02 22:54:15 DEBUG (MainThread) [zigpy.zcl] [0x7a4f:1:0xef00] ZCL deserialize: <ZCLHeader frame_control= manufacturer=None tsn=19 command_id=17>

I cant helping with the code part then im not one code worrier but i have more knowledge of the lower communication in the system.

Hope you is getting it working and also some can doing the same for the dimmers that have the same problem.

One more time great work done !!!

Barmatuhin commented 2 years ago

@javicalle can you describe once again how to implement your solution?

  1. Create new quirk file (for example ts0601_moes_2_gang_switch.py) in docker container "homeassistant"

    touch /usr/local/lib/python3.9/site-packages/zhaquirks/tuya/ts0601_moes_2_gang_switch.py

  2. Paste second part of your code

    class TuyaDoubleSwitch(TuyaSwitch):
    """Tuya double switch device."""
    
    signature = {
        # "node_descriptor": "<NodeDescriptor byte1=1 byte2=64 mac_capability_flags=142 manufacturer_code=4098
        #                       maximum_buffer_size=82 maximum_incoming_transfer_size=82 server_mask=11264
        #                       maximum_outgoing_transfer_size=82 descriptor_capability_field=0>",
        # device_version=1
        # input_clusters=[0x0000, 0x0004, 0x0005, 0xef00]
        # output_clusters=[0x000a, 0x0019]
        # <SimpleDescriptor endpoint=1 profile=260 device_type=51 device_version=1 input_clusters=[0, 4, 5, 61184] output_clusters=[10, 25]>
        MODELS_INFO: [
            ("_TZE200_g1ib5ldv", "TS0601"),
        ],
    ....
  3. What i need to do with this snippet? Put it somewhere in init.py in /tuya folder?

    class TuyaOnOff_Base(CustomCluster, OnOff):
    """Tuya On/Off base cluster for multibutton On/Off device."""
    
    _common_endpoint = None
    
    @classmethod
    def set_endpoint(cls, endpoint):
        """Set the conductor endpoint to use."""
        cls._common_endpoint = endpoint
    ...

    Did i need to clear (delete) pycache?

javicalle commented 2 years ago

I have done my quirk in a custom_quirks_path so maybe something is a little diferent.

  1. I Have created a local ts0601.py file
  2. I have putted all the classes in that file (plus imports, etc)

You can have multiple files with signatures (single switch, double, triple..) in the same file.

Barmatuhin commented 2 years ago

So in my configuration.yaml i have this record

zha: custom_quirks_path: /config/custom_zha_quirks/

so i create outside container, just open terminal (correct?)

touch /config/custom_zha_quirks/ts0601_moes_2_gang_switch.py

put all clases from two snippets remove device from ZHA integration. Emmm... did i need clear(delete) some files? pycache? Reload HA try to pair device once again Will report later, when try do this

Barmatuhin commented 2 years ago

@javicalle can you share your ts***.py file content? I put you both snippets in one py-file and zha can't load, can't find some definitions

class TuyaOnOff_Base(CustomCluster, OnOff): NameError: name 'CustomCluster' is not defined

javicalle commented 2 years ago

My py file has a lot of unrelated changes and they are going to give you more trouble than it helps.

The error that is giving you is due to the import of the dependencies that you must perform at the beginning of the file. I am attaching that part (although it will have more dependencies than you will need). That should be enough:

"""Tuya based touch switch."""
import logging
from typing import Any, Callable, Dict, List, Optional, Tuple, Union

from zigpy.profiles import zha
from zigpy.quirks import CustomCluster
import zigpy.types as t
from zigpy.zcl import Cluster, foundation
from zigpy.zcl.clusters.general import Basic, Groups, OnOff, Ota, Scenes, Time

from zhaquirks import EventableCluster
from zhaquirks.const import (
    DEVICE_TYPE,
    ENDPOINTS,
    INPUT_CLUSTERS,
    MODELS_INFO,
    OUTPUT_CLUSTERS,
    PROFILE_ID,
)

from zhaquirks.tuya import (
    Data,
    # TuyaManufacturerClusterOnOff,
    TuyaManufacturerWindowCover,
    TuyaManufCluster,
    # TuyaOnOff,
    TuyaSwitch,
    ATTR_ON_OFF,
    SWITCH_EVENT,
    TUYA_CMD_BASE,
    TUYA_SET_DATA,
)

_LOGGER = logging.getLogger(__name__)

You should also take into account that the class TuyaDoubleSwitch must be defined after the TuyaOnOff_Base class, more or less this will be the order:

Barmatuhin commented 2 years ago

Here my py-file.

"""Tuya based touch switch."""
import logging
from typing import Any, Callable, Dict, List, Optional, Tuple, Union

from zigpy.profiles import zha
from zigpy.quirks import CustomCluster
import zigpy.types as t
from zigpy.zcl import Cluster, foundation
from zigpy.zcl.clusters.general import Basic, Groups, OnOff, Ota, Scenes, Time

from zhaquirks import EventableCluster
from zhaquirks.const import (
    DEVICE_TYPE,
    ENDPOINTS,
    INPUT_CLUSTERS,
    MODELS_INFO,
    OUTPUT_CLUSTERS,
    PROFILE_ID,
)

from zhaquirks.tuya import (
    Data,
    # TuyaManufacturerClusterOnOff,
    TuyaManufacturerWindowCover,
    TuyaManufCluster,
    # TuyaOnOff,
    TuyaSwitch,
    ATTR_ON_OFF,
    SWITCH_EVENT,
    TUYA_CMD_BASE,
    TUYA_SET_DATA,
)

_LOGGER = logging.getLogger(__name__)

class TuyaOnOff_Base(CustomCluster, OnOff):
    """Tuya On/Off base cluster for multibutton On/Off device."""

    _common_endpoint = None

    @classmethod
    def set_endpoint(cls, endpoint):
        """Set the conductor endpoint to use."""
        cls._common_endpoint = endpoint

    @classmethod
    def get_endpoint(cls):
        """Get the conductor endpoint."""
        return cls._common_endpoint

    def __init__(self, *args, **kwargs):
        """Init."""
        super().__init__(*args, **kwargs)
        self.endpoint.device.switch_bus.add_listener(self)

    def switch_event(self, channel, state):
        """Switch event."""
        _LOGGER.debug(
            "%s - Received switch event message --> channel: %d, state: %d",
            self.endpoint.device.ieee,
            channel,
            state,
        )
        # update status only if event == endpoint
        if self.endpoint.endpoint_id == channel:
            self._update_attribute(ATTR_ON_OFF, state)

    def command(
        self,
        command_id: Union[foundation.Command, int, t.uint8_t],
        *args,
        manufacturer: Optional[Union[int, t.uint16_t]] = None,
        expect_reply: bool = True,
        tsn: Optional[Union[int, t.uint8_t]] = None,
    ):
        """Override the default Cluster command."""

        _LOGGER.debug(
            "%s - command --> id: %d, endpoint: %d",
            self.endpoint.device.ieee,
            command_id,
            self.endpoint.endpoint_id,
        )

        if command_id in (0x0000, 0x0001):
            cmd_payload = TuyaManufCluster.Command()
            cmd_payload.status = 0
            cmd_payload.tsn = 0
            cmd_payload.command_id = TUYA_CMD_BASE + self.endpoint.endpoint_id
            cmd_payload.function = 0
            cmd_payload.data = [1, command_id]

            return TuyaOnOff_Base.get_endpoint().tuya_manufacturer.command(
                TUYA_SET_DATA, cmd_payload, expect_reply=True
            )

        return foundation.Status.UNSUP_CLUSTER_COMMAND

class TuyaOnOff_conductor(TuyaOnOff_Base):
    """Tuya OnOff conductor button."""

    name = "TuyaOnOff_conductor"

    def __init__(self, *args, **kwargs):
        """Init."""
        super().__init__(*args, **kwargs)
        TuyaOnOff_Base.set_endpoint(self.endpoint)

class TuyaOnOff_follower(TuyaOnOff_Base):
    """Tuya OnOff fake buttons."""

    name = "TuyaOnOff_follower"

class TuyaDoubleSwitch(TuyaSwitch):
    """Tuya double switch device."""

    signature = {
        # "node_descriptor": "<NodeDescriptor byte1=1 byte2=64 mac_capability_flags=142 manufacturer_code=4098
        #                       maximum_buffer_size=82 maximum_incoming_transfer_size=82 server_mask=11264
        #                       maximum_outgoing_transfer_size=82 descriptor_capability_field=0>",
        # device_version=1
        # input_clusters=[0x0000, 0x0004, 0x0005, 0xef00]
        # output_clusters=[0x000a, 0x0019]
        # <SimpleDescriptor endpoint=1 profile=260 device_type=51 device_version=1 input_clusters=[0, 4, 5, 61184] output_clusters=[10, 25]>
        MODELS_INFO: [
            ("_TZE200_g1ib5ldv", "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,
                    TuyaManufCluster.cluster_id,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            }
        },
    }

    replacement = {
        ENDPOINTS: {
            1: {
                DEVICE_TYPE: zha.DeviceType.ON_OFF_LIGHT,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    TuyaManufacturerClusterOnOff,
                    TuyaOnOff_conductor,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            2: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.ON_OFF_LIGHT,
                INPUT_CLUSTERS: [
                    TuyaOnOff_follower,
                ],
                OUTPUT_CLUSTERS: [],
            }
        }
    }

but there is ERROR in logs

Logger: homeassistant.config_entries
Source: custom_zha_quirks/ts0601_moes2gangswitch.py:154
First occurred: 19:49:17 (1 occurrences)
Last logged: 19:49:17

Error setting up entry ConBee II - /dev/serial/by-id/usb-dresden_elektronik_ingenieurtechnik_GmbH_ConBee_II_DE2456090-if00, s/n: DE2456090 - dresden elektronik ingenieurtechnik GmbH - 1CF1:0030 for zha
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/config_entries.py", line 304, in async_setup
    result = await component.async_setup_entry(hass, self)  # type: ignore
  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 403, 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/ts0601_moes2gangswitch.py", line 117, in <module>
    class TuyaDoubleSwitch(TuyaSwitch):
  File "/config/custom_zha_quirks/ts0601_moes2gangswitch.py", line 154, in TuyaDoubleSwitch
    TuyaManufacturerClusterOnOff,
NameError: name 'TuyaManufacturerClusterOnOff' is not defined
javicalle commented 2 years ago

Uncomment it from the import part:

from zhaquirks.tuya import (
    Data,
    TuyaManufacturerClusterOnOff,  # <-- here
    TuyaManufacturerWindowCover,
    TuyaManufCluster,
    # TuyaOnOff,
    TuyaSwitch,
    ATTR_ON_OFF,
    SWITCH_EVENT,
    TUYA_CMD_BASE,
    TUYA_SET_DATA,
)
Barmatuhin commented 2 years ago

@javicalle - YOU ARE MY HERO!!!

finally it work's! I can see both EP and control them! P.S. sory for my inattention in last post... must find this commented definition by myself!

Barmatuhin commented 2 years ago

@MattWestb also wanna shake you hand!

You like guardian angel for this great HA project. Your posts in many threads like a glue between devs and us, users!

MattWestb commented 2 years ago

Great work done @javicalle and @Barmatuhin !!

More testing and then moving the class to tuya INI or leaving it in the quirk and adding all multi gang switches that is now in the "normal" single switch quirk.

I have looking and like moving all switches from the TS0601.py to the single switch and renaming it to TS0601_switch.py and renaming the original TS0601.py to TS0601_cover.py.

In the end its up to @javicalle how hi like making the support for the switches but the best is having all in the same quirk and also the single if they is working OK with the new implementation.

I can do the moving and renaming but i dont have any device to testing on so i cant helping with that.

After that i hope hi is getting one multi dimmer device so hi can fixing that 2 ;-))

javicalle commented 2 years ago

I don't think my implementation is the way to go to solve the problem. In fact I have doubts if it works in an installation with multiple switches...

I'm trying another more architecture-tuned approach to solve the problem. Give me some time to see if it is viable.

BTW, I have found the culprit for button 2 delay and I think it can be easily fixed.

MattWestb commented 2 years ago
  1. All is better then nothing for many user (but bad for the system) !!
  2. If you finding one better working code its better for the system and the user in the end and if you have better ideas its better testing if they is working better and going for the right from the beginning and not need redoing all then many user is using the code and braking things for them:-))
  3. If you do all the calls with the device address i think the system shall addressing all OK like if you is having more TRV or cover the system is connecting / linking all with the right device if the code is right done.
  4. Great you have fixing the default response on all commands sent / revived :-)))
MattWestb commented 2 years ago

The default response is waiting to being merged :-)))

I have making one PR that is renaming the single switch and putting the other switches in it so all is in one place and renaming the old ts0601.py to ts0601_cover.py for getting it nice and clean for future upgrades :-)))

Barmatuhin commented 2 years ago

@MattWestb is solution, solved in this thread will be implemented in your PR, or it will be approaching after @javicalle makes fine tune?

MattWestb commented 2 years ago

I only putting all tuya DP light switches in the same quirk that today only supporting one gang (but some multi is in it) for getting more structure in the tuya quirk folder but that can braking some test that i cant fixing but i hope its being merged.

Then its easy adding the new quirk in the old one or making one new and moving multi gang switches to it but need looking for getting the right type for all IDs added but its one later problem.

And its up to the devs deciding how to so it in there way i only trying helping getting the structure of the quirk better for the future and not braking device that is working in our systems.

@javicalle is making his own PR then hi have all working OK and is ready for being merged.

Barmatuhin commented 2 years ago

After that i hope hi is getting one multi dimmer device so hi can fixing that 2 ;-))

I also have multi dimmer :) - https://github.com/zigpy/zha-device-handlers/issues/1033 very interested and ready for making tests there :)

MattWestb commented 2 years ago

I hoping javicalle is having or getting one so hi can fixing that 2 but that is wishful thinking from my side :-))

javicalle commented 2 years ago

Hi, here is the new approach. IMHO this can be the way to go with the Tuya devices with MCU communication.

TUYA_MCU_COMMAND="tuya_mcu_command"

class TuyaMCUSwitch(TuyaSwitch):
    """Tuya device with a MCU."""

    def __init__(self, *args, **kwargs):
        """Init device with a Bus for MCU commands."""
        self.commands_bus = Bus()
        super().__init__(*args, **kwargs)

class TuyaMCUCluster(CustomCluster):
    """Tuya cluster for MCU entities communication."""

    supported_mcu_commands = ()

    def __init__(self, *args, **kwargs):
        """Init."""
        super().__init__(*args, **kwargs)
        self.supported_mcu_commands = self.server_commands.keys() # it should be all the cluster commands

    async def command(
        self,
        command_id: Union[foundation.Command, int, t.uint8_t],
        *args,
        manufacturer: Optional[Union[int, t.uint16_t]] = None,
        expect_reply: bool = True,
        tsn: Optional[Union[int, t.uint8_t]] = None,
    ):
        """Cluster commands are sent to the commands_bus listener."""

        _LOGGER.debug(
            "%s - command --> id: %d, endpoint: %d",
            self.endpoint.device.ieee,
            command_id,
            self.endpoint.endpoint_id,
        )

        if command_id in self.supported_mcu_commands:
            # commands are passed to commands_bus
            self.endpoint.device.commands_bus.listener_event(
                TUYA_MCU_COMMAND,
                self.endpoint.endpoint_id,
                command_id,
            )
            return foundation.Status.SUCCESS

        _LOGGER.warning(
            "[0x%04x:%s:0x%04x] unsupported MCU command_id: %d",
            self.endpoint.device.nwk,
            self.endpoint.endpoint_id,
            self.cluster_id,
            command_id,
        )
        return foundation.Status.UNSUP_CLUSTER_COMMAND

    def tuya_mcu_command(self, channel, command):
        """Tuya MCU command listener. Only endpoint:1 must listen to MCU commands."""
        _LOGGER.debug(
            "[0x%04x:%s:0x%04x] tuya_mcu_command --> channel: %d, command: %d",
            self.endpoint.device.nwk,
            self.endpoint.endpoint_id,
            self.cluster_id,
            channel,
            command,
        )

        if command in self.supported_mcu_commands:
            cmd_payload = TuyaManufCluster.Command()
            cmd_payload.status = 0
            cmd_payload.tsn = self.endpoint.device.application.get_sequence()
            cmd_payload.command_id = TUYA_CMD_BASE + channel
            cmd_payload.function = 0
            cmd_payload.data = [1, command]

            self.create_catching_task(
                self.endpoint.tuya_manufacturer.command(
                    TUYA_SET_DATA, cmd_payload, expect_reply=True
                )
            )
        else:
            _LOGGER.warning(
                "[0x%04x:%s:0x%04x] unsupported command: %d",
                self.endpoint.device.nwk,
                self.endpoint.endpoint_id,
                self.cluster_id,
                command,
            )

class TuyaOnOff_Base(TuyaMCUCluster, OnOff):
    """Tuya On/Off base cluster for MCU multibutton On/Off device."""

    def __init__(self, *args, **kwargs):
        """Init."""
        super().__init__(*args, **kwargs)
        self.endpoint.device.switch_bus.add_listener(self)
        self.supported_mcu_commands = (0x0000, 0x0001) # only OFF / ON are working as expected

    def switch_event(self, channel, state):
        """Switch event."""
        _LOGGER.debug(
            "%s - Received switch event message --> channel: %d, state: %d",
            self.endpoint.device.ieee,
            channel,
            state,
        )
        # update status only if event == endpoint
        if self.endpoint.endpoint_id == channel:
            self._update_attribute(ATTR_ON_OFF, state)

class TuyaOnOff_conductor(TuyaOnOff_Base):
    """Tuya On/Off cluster for endpoint:1 entity."""

    def __init__(self, *args, **kwargs):
        """Init."""
        super().__init__(*args, **kwargs)
        self.endpoint.device.commands_bus.add_listener(self) # Cluster for endpoint: 1 (listen MCU commands)

class TuyaOnOff_follower(TuyaOnOff_Base):
    """Tuya On/Off cluster for entities =! endpoint: 1."""

Probably the TuyaMCUCluster class can go to the _init_ file and each type of device generate its own MCU classes (like the TuyaMCUSwitch, TuyaOnOff_Base, ...).

The TuyaMCUDevices must define a new command_bus, and the TuyaMCUCluster will send the MCU commands to this Bus.

javicalle commented 2 years ago

I also have multi dimmer :) - #1033 very interested and ready for making tests there :)

I can give a try, but I will need to have the MCU commands. Can you activate the logs zigpy.tuya: debug and post your traces and related commands (on, off, dimm_up, dimm_down, whatever...)?

tomkusz commented 2 years ago

Uncomment it from the import part:

from zhaquirks.tuya import (
    Data,
    TuyaManufacturerClusterOnOff,  # <-- here
    TuyaManufacturerWindowCover,
    TuyaManufCluster,
    # TuyaOnOff,
    TuyaSwitch,
    ATTR_ON_OFF,
    SWITCH_EVENT,
    TUYA_CMD_BASE,
    TUYA_SET_DATA,
)

Hi @javicalle Your quirk is working, but I have the problem with adding next same model devices _TZE200_g1ib5ldv. The old device takes entity from the new one. So when I add kitchen switch first, and next i add room switch. Switch from room working well, but kitchen switch switch lights in room. The new switch takes all older switches entities function. That is strange, that entity take name from IEEE of switch from the end. For example I have switch IEEE: 5c:02:72:ff:fe:1d:b8:d9, and it's entity is: _TZE200_g1ib5ldv TS0601 d9b81dfe on_off ... Could you help me in this case?

Here is my log [0x941f:1:0x0006] ZCL deserialize: <ZCLHeader frame_control= manufacturer=None tsn=46 command_id=Command.Read_Attributes_rsp> [0x941F:1:0x0006]: finished channel initialization [0x941F:1:0x0006]: 'async_initialize' stage succeeded [0x941F:1:0x0000]: 'async_initialize' stage succeeded [0x941F:1:0x0019]: 'async_initialize' stage succeeded [0x941f:1:0xef00] ZCL deserialize: <ZCLHeader frame_control= manufacturer=None tsn=29 command_id=17> [0x941f:1:0xef00] Unknown cluster-specific command 17 [0x941f:1:0xef00] ZCL request 0x0011: b'\x00\x15@' [0x4750:1:0x0500]: Updated alarm state: 0 [0x941F:2:0x0006]: failed to get attributes '['on_off']' on 'on_off' cluster: [0x941F:2:0x0006]: async_initialize: retryable request #1 failed: . Retrying in 1.1s [0x941F:2:0x0006]: initializing channel: from_cache: False [0x941f] Extending timeout for 0x30 request [0x941F:2:0x0006]: failed to get attributes '['on_off']' on 'on_off' cluster: [0x941F:2:0x0006]: async_initialize: retryable request #2 failed: . Retrying in 1.1s [0x941F:2:0x0006]: initializing channel: from_cache: False [0x941f] Extending timeout for 0x31 request

javicalle commented 2 years ago

The new switch takes all older switches entities function.

That's what i suspected.

Can you test it with the new version of the code?

tomkusz commented 2 years ago

The new switch takes all older switches entities function.

That's what i suspected.

Can you test it with the new version of the code?

Hi Could you send me tuya_double_switch.py and init.py? I use custom_zha_quirks.

javicalle commented 2 years ago

I believe that this will work for you:

class TuyaDoubleSwitch(TuyaMCUSwitch):
    """Tuya double switch device."""

    signature = {
        # "node_descriptor": "<NodeDescriptor byte1=1 byte2=64 mac_capability_flags=142 manufacturer_code=4098
        #                       maximum_buffer_size=82 maximum_incoming_transfer_size=82 server_mask=11264
        #                       maximum_outgoing_transfer_size=82 descriptor_capability_field=0>",
        # device_version=1
        # input_clusters=[0x0000, 0x0004, 0x0005, 0xef00]
        # output_clusters=[0x000a, 0x0019]
        # <SimpleDescriptor endpoint=1 profile=260 device_type=51 device_version=1 input_clusters=[0, 4, 5, 61184] output_clusters=[10, 25]>
        MODELS_INFO: [
            ("_TZE200_g1ib5ldv", "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,
                    TuyaManufCluster.cluster_id,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            }
        },
    }

    replacement = {
        ENDPOINTS: {
            1: {
                DEVICE_TYPE: zha.DeviceType.ON_OFF_LIGHT,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    TuyaManufacturerClusterOnOff,
                    TuyaOnOff_conductor,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            2: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.ON_OFF_LIGHT,
                INPUT_CLUSTERS: [
                    TuyaOnOff_follower,
                ],
                OUTPUT_CLUSTERS: [],
            }
        }
    }

It's the same definition but must extend from the new TuyaMCUSwitch.

tomkusz commented 2 years ago

Hi

Thanks very much! I'll will check it immidiately and send response.

tomkusz commented 2 years ago

Hi

Thanks very much! I'll will check it immidiately and send response.

Hi @MattWestb Could you tell how to download files with this quirks from Codecov.

javicalle commented 2 years ago

Have you been able to get the new code to work? My quirks is not uploaded anywhere yet, so you will not be able to and will have to generate it locally, but it's the same that you already have done. Do you have any problem getting it to work?

MattWestb commented 2 years ago

Tom i was putting the latest version in the original and adding your device signature that javicalle was posted and run black on it but i cant grantee its working. Un pack the quirk and configuring local quirk in HA and putting the new quirk file in the local quirk directors you have doing and restart HA. Also adding this line in HA config for getting the debug working for the quirk (in the right place) zhaquirks: debug.

The "INIT" patch is not needed for fast testing but you is getting delay without it but its one later problem.

All information is in the thread but not so easy finding and putting together. TS0601_Switch.zip

MattWestb commented 2 years ago

For users that like getting the delay fixed i have copy one fixed tuya INIT that must being installed in the HA container for working with the local quirk.

Instruction here for editing files in the docker container https://github.com/zigpy/zha-device-handlers/discussions/693#discussion-1237451.

Copy the unzipped file to your HA config directors and from there from one command prompt in the container with:

cp /config/__init__.py  /usr/local/lib/python3.9/site-packages/zhaquirks/tuya/__init__.py

Restart HA.

init.zip

tomkusz commented 2 years ago

Hi. Unfortunately this quirk not working. I'm using Ha on debian, not in docker. Here are the logs:

Logger: homeassistant Source: components/hassio/ingress.py:247 First occurred: 23:16:32 (1 occurrences) Last logged: 23:16:32

Error doing job: Task exception was never retrieved Traceback (most recent call last): File "/usr/src/homeassistant/homeassistant/components/hassio/ingress.py", line 247, in _websocket_forward await ws_to.send_str(msg.data) File "/usr/local/lib/python3.9/site-packages/aiohttp/web_ws.py", line 300, in send_str await self._writer.send(data, binary=False, compress=compress) File "/usr/local/lib/python3.9/site-packages/aiohttp/http_websocket.py", line 687, in send await self._send_frame(message, WSMsgType.TEXT, compress) File "/usr/local/lib/python3.9/site-packages/aiohttp/http_websocket.py", line 650, in _send_frame self._write(header + message) File "/usr/local/lib/python3.9/site-packages/aiohttp/http_websocket.py", line 660, in _write raise ConnectionResetError("Cannot write to closing transport") ConnectionResetError: Cannot write to closing transport

Logger: homeassistant.config_entries Source: deps/lib/python3.9/site-packages/zhaquirks/tuya/ts0601_switch.py:42 First occurred: 23:12:48 (1 occurrences) Last logged: 23:12:48

Error setting up entry /dev/serial/by-id/usb-1a86_USB_Serial-if00-port0 for zha Traceback (most recent call last): File "/usr/src/homeassistant/homeassistant/config_entries.py", line 304, in async_setup result = await component.async_setup_entry(hass, self) # type: ignore File "/usr/src/homeassistant/homeassistant/components/zha/init.py", line 102, in async_setup_entry await zha_gateway.async_initialize() File "/usr/src/homeassistant/homeassistant/components/zha/core/gateway.py", line 152, in async_initialize self.application_controller = await app_controller_cls.new( File "/config/deps/lib/python3.9/site-packages/zigpy/application.py", line 61, in new await app._load_db() File "/config/deps/lib/python3.9/site-packages/zigpy/application.py", line 53, in _load_db await self._dblistener.load() File "/config/deps/lib/python3.9/site-packages/zigpy/appdb.py", line 383, in load device = zigpy.quirks.get_device(device) File "/config/deps/lib/python3.9/site-packages/zigpy/quirks/init.py", line 44, in get_device return _DEVICE_REGISTRY.get_device(device) File "/config/deps/lib/python3.9/site-packages/zigpy/quirks/registry.py", line 148, in get_device device = candidate(device._application, device.ieee, device.nwk, device) File "/config/deps/lib/python3.9/site-packages/zhaquirks/tuya/ts0601_switch.py", line 42, in init self.commands_bus = Bus() NameError: name 'Bus' is not defined

tomkusz commented 2 years ago

Logger: zigpy.zcl Source: deps/lib/python3.9/site-packages/zigpy/zcl/init.py:95 First occurred: 23:12:48 (4 occurrences) Last logged: 23:12:48

Unknown cluster 61184

tomkusz commented 2 years ago

Maybe should I re-pair the coordinator cc2652p ?

javicalle commented 2 years ago

You need to import the Bus class and maybe the CustomCluster. This is my full import part:

import logging
from typing import Any, Callable, Dict, List, Optional, Tuple, Union

from zigpy.profiles import zha
from zigpy.quirks import CustomCluster, CustomDevice
import zigpy.types as t
from zigpy.zcl import Cluster, foundation
from zigpy.zcl.clusters.general import Basic, Groups, OnOff, Ota, Scenes, Time

from zhaquirks import Bus, EventableCluster
from zhaquirks.const import (
    DEVICE_TYPE,
    ENDPOINTS,
    INPUT_CLUSTERS,
    MODELS_INFO,
    OUTPUT_CLUSTERS,
    PROFILE_ID,
)

from zhaquirks.tuya import (
    Data,
    TuyaManufacturerClusterOnOff,
    TuyaManufacturerWindowCover,
    TuyaManufCluster,
    # TuyaOnOff,
    TuyaSwitch,
    ATTR_ON_OFF,
    SWITCH_EVENT,
    TUYA_CMD_BASE,
    TUYA_SET_DATA,
    TUYA_SET_TIME,
)
Barmatuhin commented 2 years ago

Hello folks, Make changes from @MattWestb (new init.py in container and new quirk in local quirks). All works good. I don't see any delay when i switch virtualy in dashboard for both entitites. Maybe i need to check something in logs, can you instruct me how to found exect log information for proof that all works as designed by @javicalle? Unfortunately i have only one this MOES device (in-wall switch with 2 gangs) and can't test how it goes if i pair another one.

MattWestb commented 2 years ago

If the quirk is good made you shall getting events if you is listening for them in development tools, events and listening to events and putting in zha_event and clicking start listening for evenest.

You shall also getting them in the HA log if you have little debug logging configurated.

Can you pleas posting your working quirk if some more user like testing it and dont need going thru all you have done with "15 different not working version" ?

Barmatuhin commented 2 years ago

@MattWestb i just take your quirk file from here (https://github.com/zigpy/zha-device-handlers/issues/1038#issuecomment-962671493) and init.py file from here (https://github.com/zigpy/zha-device-handlers/issues/1038#issuecomment-962674687) And here some logs when i turn of -> turn off light from dashboard

2021-11-08 17:20:45 DEBUG (MainThread) [homeassistant.core] Bus:Handling <Event call_service[L]: domain=light, service=turn_on, service_data=entity_id=light.tze200_g1ib5ldv_ts0601_b4b41dfe_on_off_2>
2021-11-08 17:20:45 DEBUG (MainThread) [homeassistant.components.zha.core.channels.base] [0xF884:2:0x0006]: executed 'on' command with args: '()' kwargs: '{}' result: Status.SUCCESS
2021-11-08 17:20:45 DEBUG (MainThread) [homeassistant.components.zha.entity] light.tze200_g1ib5ldv_ts0601_b4b41dfe_on_off_2: turned on: {'on_off': <Status.SUCCESS: 0>}
2021-11-08 17:20:45 DEBUG (MainThread) [zigpy.device] [0xf884] Extending timeout for 0xd5 request
2021-11-08 17:20:45 DEBUG (MainThread) [zigpy_deconz.zigbee.application] Sending Zigbee request with tsn 213 under 214 request id, data: b'050210d50000d40201000101'
2021-11-08 17:20:45 DEBUG (MainThread) [zigpy_deconz.api] Command Command.aps_data_request (27, 214, 0, <DeconzAddressEndpoint address_mode=2 address=0xF884 endpoint=1>, 260, 61184, 1, b'\x05\x02\x10\xd5\x00\x00\xd4\x02\x01\x00\x01\x01', 2, 0)
2021-11-08 17:20:45 DEBUG (MainThread) [zigpy_deconz.api] APS data request response: [2, <DeviceState.APSDE_DATA_REQUEST_SLOTS_AVAILABLE|2: 34>, 214]
2021-11-08 17:20:45 DEBUG (MainThread) [zigpy_deconz.api] Device state changed response: [<DeviceState.128|APSDE_DATA_REQUEST_SLOTS_AVAILABLE|APSDE_DATA_CONFIRM|2: 166>, 0]
2021-11-08 17:20:45 DEBUG (MainThread) [zigpy_deconz.api] Command Command.aps_data_confirm (0,)
2021-11-08 17:20:45 DEBUG (MainThread) [zigpy_deconz.api] APS data confirm response for request with id 214: 00
2021-11-08 17:20:45 DEBUG (MainThread) [zigpy_deconz.api] Request id: 0xd6 'aps_data_confirm' for <DeconzAddressEndpoint address_mode=ADDRESS_MODE.NWK address=0xf884 endpoint=1>, status: 0x00
2021-11-08 17:20:45 DEBUG (MainThread) [zigpy_deconz.api] Device state changed response: [<DeviceState.128|APSDE_DATA_REQUEST_SLOTS_AVAILABLE|APSDE_DATA_INDICATION|2: 170>, 0]
2021-11-08 17:20:45 DEBUG (MainThread) [zigpy_deconz.api] Command Command.aps_data_indication (1, 1)
2021-11-08 17:20:45 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=0xf884>, 1, 260, 61184, b'\t\x17\x01\x00\xd4\x02\x01\x00\x01\x01', 0, 175, 239, 69, 59, 2, 0, -64]
2021-11-08 17:20:45 DEBUG (MainThread) [zigpy.zcl] [0xf884:1:0xef00] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=CLUSTER_COMMAND manufacturer_specific=False is_reply=True disable_default_response=False> manufacturer=None tsn=23 command_id=1>
2021-11-08 17:20:45 DEBUG (MainThread) [zigpy.zcl] [0xf884:1:0xef00] ZCL request 0x0001: [Command(status=0, tsn=212, command_id=258, function=0, data=[1, 1])]
2021-11-08 17:20:45 DEBUG (MainThread) [zigpy_deconz.zigbee.application] Sending Zigbee request with tsn 23 under 215 request id, data: b'18170b0100'
2021-11-08 17:20:45 DEBUG (MainThread) [homeassistant.core] Bus:Handling <Event state_changed[L]: entity_id=light.tze200_g1ib5ldv_ts0601_b4b41dfe_on_off_2, old_state=<state light.tze200_g1ib5ldv_ts0601_b4b41dfe_on_off_2=off; supported_color_modes=['onoff'], off_brightness=None, friendly_name=Свет ванная, supported_features=0 @ 2021-11-08T17:07:43.371747+02:00>, new_state=<state light.tze200_g1ib5ldv_ts0601_b4b41dfe_on_off_2=on; supported_color_modes=['onoff'], color_mode=onoff, off_brightness=None, friendly_name=Свет ванная, supported_features=0 @ 2021-11-08T17:20:45.733369+02:00>>
2021-11-08 17:20:45 DEBUG (MainThread) [zigpy_deconz.api] 'aps_data_indication' response from <DeconzAddress address_mode=ADDRESS_MODE.NWK address=0xf884>, ep: 1, profile: 0x0104, cluster_id: 0xef00, data: b'09170100d40201000101'
2021-11-08 17:20:45 DEBUG (MainThread) [zigpy_deconz.api] Command Command.aps_data_request (20, 215, 0, <DeconzAddressEndpoint address_mode=2 address=0xF884 endpoint=1>, 260, 61184, 1, b'\x18\x17\x0b\x01\x00', 2, 0)
2021-11-08 17:20:45 DEBUG (MainThread) [zigpy_deconz.api] Device state changed response: [<DeviceState.128|APSDE_DATA_REQUEST_SLOTS_AVAILABLE|APSDE_DATA_INDICATION|2: 170>, 0]
2021-11-08 17:20:45 DEBUG (MainThread) [zigpy_deconz.api] Command Command.aps_data_indication (1, 1)
2021-11-08 17:20:45 DEBUG (MainThread) [zigpy_deconz.api] APS data indication response: [30, <DeviceState.APSDE_DATA_REQUEST_SLOTS_AVAILABLE|2: 34>, <DeconzAddress address_mode=ADDRESS_MODE.NWK address=0x0000>, 1, <DeconzAddress address_mode=ADDRESS_MODE.NWK address=0xf884>, 1, 260, 61184, b'\x0c\x02\x10\xd5\x0b\x00\x00', 0, 175, 239, 130, 59, 2, 0, -64]
2021-11-08 17:20:45 DEBUG (MainThread) [zigpy.zcl] [0xf884:1:0xef00] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=True is_reply=True disable_default_response=False> manufacturer=4098 tsn=213 command_id=Command.Default_Response>
2021-11-08 17:20:45 DEBUG (MainThread) [zigpy_deconz.api] 'aps_data_indication' response from <DeconzAddress address_mode=ADDRESS_MODE.NWK address=0xf884>, ep: 1, profile: 0x0104, cluster_id: 0xef00, data: b'0c0210d50b0000'
2021-11-08 17:20:45 DEBUG (MainThread) [zigpy_deconz.api] Device state changed response: [<DeviceState.128|APSDE_DATA_REQUEST_SLOTS_AVAILABLE|APSDE_DATA_CONFIRM|2: 166>, 0]
2021-11-08 17:20:45 DEBUG (MainThread) [zigpy_deconz.api] Command Command.aps_data_confirm (0,)
2021-11-08 17:20:45 DEBUG (MainThread) [zigpy_deconz.api] APS data confirm response for request with id 215: 00
2021-11-08 17:20:45 DEBUG (MainThread) [zigpy_deconz.api] Request id: 0xd7 'aps_data_confirm' for <DeconzAddressEndpoint address_mode=ADDRESS_MODE.NWK address=0xf884 endpoint=1>, status: 0x00
2021-11-08 17:20:46 DEBUG (MainThread) [homeassistant.core] Bus:Handling <Event call_service[L]: domain=light, service=turn_off, service_data=entity_id=light.tze200_g1ib5ldv_ts0601_b4b41dfe_on_off_2>
2021-11-08 17:20:46 DEBUG (MainThread) [homeassistant.components.zha.core.channels.base] [0xF884:2:0x0006]: executed 'off' command with args: '()' kwargs: '{}' result: Status.SUCCESS
2021-11-08 17:20:46 DEBUG (MainThread) [homeassistant.components.zha.entity] light.tze200_g1ib5ldv_ts0601_b4b41dfe_on_off_2: turned off: Status.SUCCESS
2021-11-08 17:20:46 DEBUG (MainThread) [zigpy.device] [0xf884] Extending timeout for 0xd9 request
2021-11-08 17:20:46 DEBUG (MainThread) [zigpy_deconz.zigbee.application] Sending Zigbee request with tsn 217 under 218 request id, data: b'050210d90000d80201000100'
2021-11-08 17:20:46 DEBUG (MainThread) [zigpy_deconz.api] Command Command.aps_data_request (27, 218, 0, <DeconzAddressEndpoint address_mode=2 address=0xF884 endpoint=1>, 260, 61184, 1, b'\x05\x02\x10\xd9\x00\x00\xd8\x02\x01\x00\x01\x00', 2, 0)
2021-11-08 17:20:46 DEBUG (MainThread) [zigpy_deconz.api] APS data request response: [2, <DeviceState.APSDE_DATA_REQUEST_SLOTS_AVAILABLE|2: 34>, 218]
2021-11-08 17:20:47 DEBUG (MainThread) [zigpy_deconz.api] Device state changed response: [<DeviceState.128|APSDE_DATA_REQUEST_SLOTS_AVAILABLE|APSDE_DATA_CONFIRM|2: 166>, 0]
2021-11-08 17:20:47 DEBUG (MainThread) [zigpy_deconz.api] Command Command.aps_data_confirm (0,)
2021-11-08 17:20:47 DEBUG (MainThread) [zigpy_deconz.api] APS data confirm response for request with id 218: 00
2021-11-08 17:20:47 DEBUG (MainThread) [zigpy_deconz.api] Request id: 0xda 'aps_data_confirm' for <DeconzAddressEndpoint address_mode=ADDRESS_MODE.NWK address=0xf884 endpoint=1>, status: 0x00
2021-11-08 17:20:47 DEBUG (MainThread) [zigpy_deconz.api] Device state changed response: [<DeviceState.128|APSDE_DATA_REQUEST_SLOTS_AVAILABLE|APSDE_DATA_INDICATION|2: 170>, 0]
2021-11-08 17:20:47 DEBUG (MainThread) [zigpy_deconz.api] Command Command.aps_data_indication (1, 1)
2021-11-08 17:20:47 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=0xf884>, 1, 260, 61184, b'\t\x18\x01\x00\xd8\x02\x01\x00\x01\x00', 0, 175, 239, 69, 59, 2, 0, -64]
2021-11-08 17:20:47 DEBUG (MainThread) [zigpy.zcl] [0xf884:1:0xef00] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=CLUSTER_COMMAND manufacturer_specific=False is_reply=True disable_default_response=False> manufacturer=None tsn=24 command_id=1>
2021-11-08 17:20:47 DEBUG (MainThread) [zigpy.zcl] [0xf884:1:0xef00] ZCL request 0x0001: [Command(status=0, tsn=216, command_id=258, function=0, data=[1, 0])]
2021-11-08 17:20:47 DEBUG (MainThread) [zigpy_deconz.zigbee.application] Sending Zigbee request with tsn 24 under 219 request id, data: b'18180b0100'
2021-11-08 17:20:47 DEBUG (MainThread) [homeassistant.core] Bus:Handling <Event state_changed[L]: entity_id=light.tze200_g1ib5ldv_ts0601_b4b41dfe_on_off_2, old_state=<state light.tze200_g1ib5ldv_ts0601_b4b41dfe_on_off_2=on; supported_color_modes=['onoff'], color_mode=onoff, off_brightness=None, friendly_name=Свет ванная, supported_features=0 @ 2021-11-08T17:20:45.733369+02:00>, new_state=<state light.tze200_g1ib5ldv_ts0601_b4b41dfe_on_off_2=off; supported_color_modes=['onoff'], off_brightness=None, friendly_name=Свет ванная, supported_features=0 @ 2021-11-08T17:20:47.139395+02:00>>
2021-11-08 17:20:47 DEBUG (MainThread) [zigpy_deconz.api] 'aps_data_indication' response from <DeconzAddress address_mode=ADDRESS_MODE.NWK address=0xf884>, ep: 1, profile: 0x0104, cluster_id: 0xef00, data: b'09180100d80201000100'
2021-11-08 17:20:47 DEBUG (MainThread) [zigpy_deconz.api] Command Command.aps_data_request (20, 219, 0, <DeconzAddressEndpoint address_mode=2 address=0xF884 endpoint=1>, 260, 61184, 1, b'\x18\x18\x0b\x01\x00', 2, 0)
2021-11-08 17:20:47 DEBUG (MainThread) [zigpy_deconz.api] APS data request response: [2, <DeviceState.APSDE_DATA_REQUEST_SLOTS_AVAILABLE|2: 34>, 219]
2021-11-08 17:20:47 DEBUG (MainThread) [zigpy_deconz.api] Device state changed response: [<DeviceState.128|APSDE_DATA_REQUEST_SLOTS_AVAILABLE|APSDE_DATA_INDICATION|2: 170>, 0]
2021-11-08 17:20:47 DEBUG (MainThread) [zigpy_deconz.api] Command Command.aps_data_indication (1, 1)
2021-11-08 17:20:47 DEBUG (MainThread) [zigpy_deconz.api] APS data indication response: [30, <DeviceState.APSDE_DATA_REQUEST_SLOTS_AVAILABLE|2: 34>, <DeconzAddress address_mode=ADDRESS_MODE.NWK address=0x0000>, 1, <DeconzAddress address_mode=ADDRESS_MODE.NWK address=0xf884>, 1, 260, 61184, b'\x0c\x02\x10\xd9\x0b\x00\x00', 0, 175, 231, 130, 59, 2, 0, -65]
2021-11-08 17:20:47 DEBUG (MainThread) [zigpy.zcl] [0xf884:1:0xef00] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=True is_reply=True disable_default_response=False> manufacturer=4098 tsn=217 command_id=Command.Default_Response>
2021-11-08 17:20:47 DEBUG (MainThread) [zigpy_deconz.api] 'aps_data_indication' response from <DeconzAddress address_mode=ADDRESS_MODE.NWK address=0xf884>, ep: 1, profile: 0x0104, cluster_id: 0xef00, data: b'0c0210d90b0000'
2021-11-08 17:20:47 DEBUG (MainThread) [zigpy_deconz.api] Device state changed response: [<DeviceState.128|APSDE_DATA_REQUEST_SLOTS_AVAILABLE|APSDE_DATA_CONFIRM|2: 166>, 0]
2021-11-08 17:20:47 DEBUG (MainThread) [zigpy_deconz.api] Command Command.aps_data_confirm (0,)
2021-11-08 17:20:47 DEBUG (MainThread) [zigpy_deconz.api] APS data confirm response for request with id 219: 00
2021-11-08 17:20:47 DEBUG (MainThread) [zigpy_deconz.api] Request id: 0xdb 'aps_data_confirm' for <DeconzAddressEndpoint address_mode=ADDRESS_MODE.NWK address=0xf884 endpoint=1>, status: 0x00
tomkusz commented 2 years ago

You need to import the Bus class and maybe the CustomCluster. This is my full import part:

import logging
from typing import Any, Callable, Dict, List, Optional, Tuple, Union

from zigpy.profiles import zha
from zigpy.quirks import CustomCluster, CustomDevice
import zigpy.types as t
from zigpy.zcl import Cluster, foundation
from zigpy.zcl.clusters.general import Basic, Groups, OnOff, Ota, Scenes, Time

from zhaquirks import Bus, EventableCluster
from zhaquirks.const import (
    DEVICE_TYPE,
    ENDPOINTS,
    INPUT_CLUSTERS,
    MODELS_INFO,
    OUTPUT_CLUSTERS,
    PROFILE_ID,
)

from zhaquirks.tuya import (
    Data,
    TuyaManufacturerClusterOnOff,
    TuyaManufacturerWindowCover,
    TuyaManufCluster,
    # TuyaOnOff,
    TuyaSwitch,
    ATTR_ON_OFF,
    SWITCH_EVENT,
    TUYA_CMD_BASE,
    TUYA_SET_DATA,
    TUYA_SET_TIME,
)

Hi @javicalle

Thank you very much!

With your code is much better, but now I have info tuya_mcu_command is not defined. I put your code to switch.py, is that correct?

Logger: homeassistant.components.websocket_api.http.connection Source: deps/lib/python3.9/site-packages/zhaquirks/tuya/ts0601_switch.py:79 Integration: Home Assistant WebSocket API (documentation, issues) First occurred: 21:03:28 (13 occurrences) Last logged: 21:09:49

[3966183000] name 'TUYA_MCU_COMMAND' is not defined Traceback (most recent call last): File "/usr/src/homeassistant/homeassistant/components/websocket_api/commands.py", line 185, in handle_call_service await hass.services.async_call( File "/usr/src/homeassistant/homeassistant/core.py", line 1495, in async_call task.result() File "/usr/src/homeassistant/homeassistant/core.py", line 1530, in _execute_service await handler.job.target(service_call) File "/usr/src/homeassistant/homeassistant/helpers/entity_component.py", line 213, in handle_service await self.hass.helpers.service.entity_service_call( File "/usr/src/homeassistant/homeassistant/helpers/service.py", line 667, in entity_service_call future.result() # pop exception if have File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 863, in async_request_call await coro File "/usr/src/homeassistant/homeassistant/helpers/service.py", line 704, in _handle_entity_call await result File "/usr/src/homeassistant/homeassistant/components/light/init.py", line 496, in async_handle_light_on_service await light.async_turn_on(*filter_turn_on_params(light, params)) File "/usr/src/homeassistant/homeassistant/components/zha/light.py", line 246, in async_turn_on result = await self._on_off_channel.on() File "/usr/src/homeassistant/homeassistant/components/zha/core/channels/base.py", line 59, in wrapper result = await command(args, **kwds) File "/config/deps/lib/python3.9/site-packages/zhaquirks/tuya/ts0601_switch.py", line 79, in command TUYA_MCU_COMMAND, NameError: name 'TUYA_MCU_COMMAND' is not defined

javicalle commented 2 years ago

@tomkusz your installation complains about the TUYA_MCU_COMMAND definition. You must make sure that the definition of the variable is in the file, above where it is being used in the code. See how it is defined here:

tomkusz commented 2 years ago

@javicalle you are great, it's working now perfectly!!! That's my fault with no TUYA_MCU_COMMAND definition in switch.py.

I have one more problem with Tuya 1-button (TS0041 _TZ3000_4upl1fcj) and 2-button (TS0042 _TZ3000_dfgbtub0) Scene Switches. There is no buttons entities (there are only battery entities). Mayby you have medicine for it ...

You are very good Tuya Device Doctor!

tomkusz commented 2 years ago

@javicalle It will be great if those Scene Switches can work with long, short and double push of the buttons.

javicalle commented 2 years ago

I have one more problem with Tuya 1-button (TS0041 _TZ3000_4upl1fcj) and 2-button (TS0042 _TZ3000_dfgbtub0) Scene Switches. There is no buttons entities (there are only battery entities).

These kind of switches are not exposed as buttons in HA. From HA you can read the events of type zha_event or activate actions with triggers that defines (if any is defined).

Try to create in HA a new automation for these devices and you will be able to check which triggers this device publishes. If you have a problem, please create a new ticket with the incident and all the data you have to help solve it.