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
719 stars 664 forks source link

Tuya Curtain Motor #744

Closed tube0013 closed 10 months ago

tube0013 commented 3 years ago

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

Request and placehoder for information about the Tuya Curtain motor > specfically:

https://zigbee.blakadder.com/Moes_AM43-0_45_40-ES-EB.html

z2m device: https://github.com/Koenkk/zigbee-herdsman-converters/blob/9e4c36cd79e15449830f5e09540157c0e8d686a1/devices.js#L1481

z2m fromZigbee: https://github.com/Koenkk/zigbee-herdsman-converters/blob/9e4c36cd79e15449830f5e09540157c0e8d686a1/converters/fromZigbee.js#L1490

z2m toZigbee: https://github.com/Koenkk/zigbee-herdsman-converters/blob/9e4c36cd79e15449830f5e09540157c0e8d686a1/converters/toZigbee.js#L3377

Describe the solution you'd like

ZHA support :)

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(byte1=2, byte2=64, mac_capability_flags=128, manufacturer_code=4098, maximum_buffer_size=82, maximum_incoming_transfer_size=82, server_mask=11264, maximum_outgoing_transfer_size=82, descriptor_capability_field=0)",
  "endpoints": {
    "1": {
      "profile_id": 260,
      "device_type": "0x0051",
      "in_clusters": [
        "0x0000",
        "0x0004",
        "0x0005",
        "0xef00"
      ],
      "out_clusters": [
        "0x000a",
        "0x0019"
      ]
    }
  },
  "manufacturer": "_TZE200_rddyvrci",
  "model": "TS0601",
  "class": "zigpy.device.Device"
}

Additional context Add any other context or screenshots about the feature request here.

For reference since the only place I found it was in the review comments on Aliexpress. to get into pairing mode, hold the set and down button simultaneously until light blinks.

Adminiuga commented 3 years ago

Tuya has become just one huge PITA. Why they couldn't at least differentiate by device type? This kills the idea of using "wildcard" manufacturers quirk, because two different devices have exactly the same signature.

dmulcahey commented 3 years ago

As long as a direct match always wins I wouldn’t say this kills it...

Adminiuga commented 3 years ago

That's true. Just means that we need to pick for which devices to use a wildcard match and for which ones to use model+manufacturer specific matches. I just hate they are that lazy to change at least the device id

tube0013 commented 3 years ago

all these modules come from tuya with the same TS0601 model to the manufacturing shops.

the manufactures hit up the tuya sdk site flip a couple things or follow steps from tuya to configure the module for the type of device, output a fw, and push it to the devices. they also add their code for the manufacturer possibly based on the PID or derived from it - you see this mentioned in the tuya docs and what the factory I communicated with kept mentioning.

SHxKM commented 3 years ago

I just moved to ZHA from Deconz + Phoscon and I if I'm not mistaken implementing this one will solve the integration for every QS-Zigbee-C01 curtain module? This one needs calibration upon first use, and as mentioned in this Home Assistant Community thread, this attribute isn't exposed at all currently through the clusters.

I'm just getting my feet wet with ZHA quirks and while I've been with Python for a while now, adding functionality etc...seems less intimidating in Zigbee2MQTT than in ZHA quirks. I guess it's just a matter of getting more familiar with Zigbee itself. Any pointers on where I would start to expose the relevant cluster attributes would be super appreciated.

hertznsk commented 3 years ago

I have a curtain Zemismart ZM79E-DT (Tuya). 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)",
  "endpoints": {
    "1": {
      "profile_id": 260,
      "device_type": "0x0051",
      "in_clusters": [
        "0x0000",
        "0x0004",
        "0x0005",
        "0x000a",
        "0xef00"
      ],
      "out_clusters": [
        "0x0019"
      ]
    }
  },
  "manufacturer": "_TZE200_cowvfni3",
  "model": "TS0601",
  "class": "zigpy.device.Device"
}

Protocol description: https://github.com/Koenkk/zigbee-herdsman-converters/issues/1159#issuecomment-614659802

rednus commented 3 years ago

I have Moeshouse one from here: https://zigbee.blakadder.com/Moes_AM43-0_45_40-ES-EB.html

Paired to ZHA but doesn't show any entities or services..

Below signature: { "node_descriptor": "NodeDescriptor(byte1=2, byte2=64, mac_capability_flags=128, manufacturer_code=4098, maximum_buffer_size=82, maximum_incoming_transfer_size=82, server_mask=11264, maximum_outgoing_transfer_size=82, descriptor_capability_field=0)", "endpoints": { "1": { "profile_id": 260, "device_type": "0x0051", "in_clusters": [ "0x0000", "0x0004", "0x0005", "0xef00" ], "out_clusters": [ "0x000a", "0x0019" ] } }, "manufacturer": "_TZE200_zah67ekd", "model": "TS0601", "class": "zigpy.device.Device" }

alekslyse commented 3 years ago

Zemismart here https://zigbee.blakadder.com/Zemismart_M515EGB.html

{ "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)", "endpoints": { "1": { "profile_id": 260, "device_type": "0x0051", "in_clusters": [ "0x0000", "0x0004", "0x0005", "0xef00" ], "out_clusters": [ "0x000a", "0x0019" ] } }, "manufacturer": "_TZE200_xuzcvlku", "model": "TS0601", "class": "zigpy.device.Device" }

alekslyse commented 3 years ago

It seems like if someone make a quirk for one we can just copy them. They seem like the same product, just different manufacturer

rdhlb commented 3 years ago

I've also got roller blinds motor AM43-0.45/40-ES-EB.

Recognized by ZHA as _TZE200_zah67ekd TS0601.

Signature:

{
  "node_descriptor": "NodeDescriptor(byte1=2, byte2=64, mac_capability_flags=128, manufacturer_code=4098, maximum_buffer_size=82, maximum_incoming_transfer_size=82, server_mask=11264, maximum_outgoing_transfer_size=82, descriptor_capability_field=0)",
  "endpoints": {
    "1": {
      "profile_id": 260,
      "device_type": "0x0051",
      "in_clusters": [
        "0x0000",
        "0x0004",
        "0x0005",
        "0xef00"
      ],
      "out_clusters": [
        "0x000a",
        "0x0019"
      ]
    }
  },
  "manufacturer": "_TZE200_zah67ekd",
  "model": "TS0601",
  "class": "zigpy.device.Device"
}

I've got some Python experience, but I'm new to the ecosystem. Will be ready to help if someone guides me from where to start.

rednus commented 3 years ago

I am not an expert but managed to do some copy+paste magic and at least make the first step towards working quirk..

So ZHA recognises my quirk and assigns Up/Down/Stop buttons.. but commands are not in right order clicking Up works, but Stop and Down seems to be swapped..

image

Any advice highly appreciated.. below my code..

Add below code to the end of /usr/local/lib/python3.8/site-packages/zhaquirks/tuya/init.py

########################################################################################

from zigpy.zcl.clusters.closures import WindowCovering

class TuyaOpenClose(CustomCluster, WindowCovering):
    """Tuya Open/Close cluster for Blind Controller device."""

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

    def switch_event(self, channel, state):
        """Switch event."""
        _LOGGER.info(
            "%s - -----------------Received blind event message, channel: %d, state: %d",
            self.endpoint.device.ieee,
            channel,
            state,
        )
        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."""

        if command_id in (0x0000, 0x0001, 0x0002):
            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 self.endpoint.tuya_manufacturer.command(
                TUYA_SET_DATA, cmd_payload, expect_reply=True
            )

        return foundation.Status.UNSUP_CLUSTER_COMMAND

class TuyaManufacturerClusterOpenClose(TuyaManufCluster):
    """Manufacturer Specific Cluster of Open/Close device."""

    def handle_cluster_request(
        self,
        hdr: foundation.ZCLHeader,
        args: Tuple[TuyaManufCluster.Command],
        *,
        dst_addressing: Optional[
            Union[t.Addressing.Group, t.Addressing.IEEE, t.Addressing.NWK]
        ] = None,
    ) -> None:
        """Handle cluster request."""

        tuya_payload = args[0]
        if hdr.command_id in (0x0002, 0x0001, 0x0000):
            self.endpoint.device.blind_bus.listener_event(
                SWITCH_EVENT,
                tuya_payload.command_id - TUYA_CMD_BASE,
                tuya_payload.data[1],
            )

class TuyaBlinds(CustomDevice):
    """Generic Tuya thermostat device."""

    def __init__(self, *args, **kwargs):
        """Init device."""
        self.blind_bus = Bus()
        self.ui_bus = Bus()
        self.battery_bus = Bus()
        super().__init__(*args, **kwargs)

Then create a new file called /usr/local/lib/python3.8/site-packages/zhaquirks/tuya/blinds.py with code below:

"""Tuya based blind controller."""
from zigpy.profiles import zha
from zigpy.zcl.clusters.general import Basic, Groups, Ota, Scenes, Time

from ..const import (
    DEVICE_TYPE,
    ENDPOINTS,
    INPUT_CLUSTERS,
    MODELS_INFO,
    OUTPUT_CLUSTERS,
    PROFILE_ID,
)
from ..tuya import TuyaManufacturerClusterOnOff, TuyaManufCluster, TuyaOnOff, TuyaSwitch, TuyaBlinds, TuyaManufacturerClusterOpenClose, TuyaOpenClose

class MoesBlindController(TuyaBlinds):
    """Tuya blind controller device."""

    signature = {
        #"node_descriptor": "NodeDescriptor(byte1=2, byte2=64, mac_capability_flags=128, manufacturer_code=4098, 
        #                    maximum_buffer_size=82, maximum_incoming_transfer_size=82, server_mask=11264, 
        #                    maximum_outgoing_transfer_size=82, descriptor_capability_field=0)",
        # "endpoints": {
        # "1": {
        # "profile_id": 260,
        # "device_type": "0x0051",
        # "in_clusters": [
        #   "0x0000",
        #   "0x0004",
        #   "0x0005",
        #   "0xef00"
        # ],
        # "out_clusters": [
        #   "0x000a",
        #   "0x0019"
        # ]
        # }
        # },
        # "manufacturer": "_TZE200_zah67ekd",
        # "model": "TS0601",
        # "class": "zigpy.device.Device"
        # }
        MODELS_INFO: [("_TZE200_zah67ekd", "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.WINDOW_COVERING_DEVICE,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    TuyaManufacturerClusterOpenClose,
                    TuyaOpenClose,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            }
        }
    }
blauret commented 3 years ago

I made a quirks which is working with my Zemismart ZM25TQ. It's here.

If you look at the implementation in mqtt2zigbee there are quite few differences but it should be possible to support all the various devices listed here

tube0013 commented 3 years ago

I made a quirks which is working with my Zemismart ZM25TQ. It's here.

If you look at the implementation in mqtt2zigbee there are quite few differences but it should be possible to support all the various devices listed here

Thank you!

I gave this a shot with my MOES based one after adjusting the signature to match it, it functions however the buttons are not triggering correctly.

Down: motor turns counter clockwise Stop: motor turns clockwise Up: motor stops

I saw the mapping in init.py for the command but not picking up on how it's done. if you point me in the right direction I can hopefully add support for the moes as well.

blauret commented 3 years ago

The quirks receives from HA:

    0x0000: ("up_open", (), False),
    0x0001: ("down_close", (), False),
    0x0002: ("stop", (), False),

The motor I tested wants:

To control properly I map:

1 -> 2 2 -> 1

In your case I guess the right mapping should be

TUYA_COVER_COMMAND = {0x0000: 0x0001, 0x0001: 0x0002, 0x0002: 0x0000}

alekslyse commented 3 years ago

I made a quirks which is working with my Zemismart ZM25TQ. It's here.

If you look at the implementation in mqtt2zigbee there are quite few differences but it should be possible to support all the various devices listed here

Could you add my model "manufacturer": "_TZE200_xuzcvlku","model": "TS0601" to your PR - From what I can see they are identical, and you can add several models to your quirk (same vendor)

ScottEgan commented 3 years ago

Excuse my ignorance, as I am no expert, but how do we make these changes work for everyone?

Based on the observations from @tube0013 it seems as though each device might have slightly different control mapping. Reviewing the changes, it looks like the mapping is included in the __init__.py file. Would we have to add a new line like this for every different device?

TUYA_COVER_COMMAND = {0x0000: 0x0000, 0x0001: 0x0002, 0x0002: 0x0001}
TUYA_COVER_COMMAND_2 = {0x0000: 0x0001, 0x0001: 0x0002, 0x0002: 0x0000}

And then somehow reference that line in the manufacturer specific quirk file TS0601.py? Or do we create another class in the __init__.py file and then reference that in the manufacturer specific file?

Again, sorry if this sounds dumb I'm just trying to wrap my head around this.

blauret commented 3 years ago

Again, sorry if this sounds dumb I'm just trying to wrap my head around this.

I don't claim to have known what I was doing when I wrote this code ;)

It's the first Tuya cover quirks. looking at zigbee2mqtt there are lots of variation. I did not think about how the code would scale for other devices.

blauret commented 3 years ago

Could you add my model "manufacturer": "_TZE200_xuzcvlku","model": "TS0601" to your PR - From what I can see they are identical, and you can add several models to your quirk (same vendor)

Done

ScottEgan commented 3 years ago

I don't claim to have known what I was doing when I wrote this code ;)

You got farther than I did! Thank you for getting us this far!

Maybe someone with some more experience can chime in on what the typical procedure is for handling multiple devices with small differences?

rednus commented 3 years ago

@blauret could you please add the below code to ts0601.py The In and out clusters are different on Moes.

PS: I have tested it and it works..

class TuyaMoesCover0601(TuyaWindowCover):
    """Tuya blind controller device."""

    signature = {
        #"node_descriptor": "NodeDescriptor(byte1=2, byte2=64, mac_capability_flags=128, manufacturer_code=4098, 
        #                    maximum_buffer_size=82, maximum_incoming_transfer_size=82, server_mask=11264, 
        #                    maximum_outgoing_transfer_size=82, descriptor_capability_field=0)",
        # "endpoints": {
        # "1": { "profile_id": 260, "device_type": "0x0051", "in_clusters": [ "0x0000", "0x0004","0x0005","0xef00"], "out_clusters": ["0x000a","0x0019"] }
        # },
        # "manufacturer": "_TZE200_zah67ekd",
        # "model": "TS0601",
        # "class": "zigpy.device.Device"
        # }
        MODELS_INFO: [("_TZE200_zah67ekd", "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.WINDOW_COVERING_DEVICE,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    TuyaManufacturerWindowCover,
                    TuyaWindowCoverControl,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            }
        }
    }
daniel-sanche commented 3 years ago

Thanks @rednus, I have the same model and confirm that this works for me too. (Even the buttons are correct!)

blauret commented 3 years ago

Thanks @rednus, I have the same model and confirm that this works for me too. (Even the buttons are correct!)

Added

rednus commented 3 years ago

Thank you @blauret

hertznsk commented 3 years ago

I used a quirk https://github.com/zigpy/zha-device-handlers/pull/801 for my Zemismart ZM79E-DT, adding "_TZE200_cowvfni3", but the correct operation of the buttons is provided with the following line: TUYA_COVER_COMMAND = {0x0000: 0x0002, 0x0001: 0x0000, 0x0002: 0x0001}

rednus commented 3 years ago

@blauret - is your device working with "SET COVER POSITION"???

For me full open and full close working.. but using slider to open to a set position does not.

I am getting below error:

  File "/usr/src/homeassistant/homeassistant/components/zha/core/channels/base.py", line 49, in wrapper,
    result = await command(*args, **kwds),
    res = await self._cover_channel.go_to_lift_percentage(100 - new_pos),
  File "/usr/src/homeassistant/homeassistant/components/zha/cover.py", line 138, in async_set_cover_position,
TypeError: object Status can't be used in 'await' expression,
    await result,
  File "/usr/src/homeassistant/homeassistant/helpers/service.py", line 679, in _handle_entity_call,
    await coro,
  File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 681, in async_request_call,
    future.result()  # pop exception if have,
  File "/usr/src/homeassistant/homeassistant/helpers/service.py", line 642, in entity_service_call,
    await self.hass.helpers.service.entity_service_call(,
  File "/usr/src/homeassistant/homeassistant/helpers/entity_component.py", line 204, in handle_service,
    await handler.job.target(service_call),
  File "/usr/src/homeassistant/homeassistant/core.py", line 1523, in _execute_service,
    task.result(),
  File "/usr/src/homeassistant/homeassistant/core.py", line 1488, in async_call,
    await hass.services.async_call(,
  File "/usr/src/homeassistant/homeassistant/components/websocket_api/commands.py", line 137, in handle_call_service,
Traceback (most recent call last):,
2021-03-07 23:42:54 ERROR (MainThread) [homeassistant.components.websocket_api.http.connection] [140254504932400] object Status can't be used in 'await' expression
blauret commented 3 years ago

It won't work. the mapping ("TUYA_COVER_COMMAND") is used for all command ids. Command id 2 should get, I believe a 4 bytes data with the percentage in the lowest byte.

the code should look something like that:

@@ -608,12 +608,13 @@ class TuyaWindowCoverControl(LocalDataCluster, WindowCovering):
     ):
         """Override the default Cluster command."""

-        if command_id in (0x0000, 0x0001, 0x0002):
-            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 = 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
+
+        if command_id == 0x0001:
             cmd_payload.data = [
                 1,
                 TUYA_COVER_COMMAND[command_id],
@@ -622,6 +623,16 @@ class TuyaWindowCoverControl(LocalDataCluster, WindowCovering):
             return self.endpoint.tuya_manufacturer.command(
                 TUYA_SET_DATA, cmd_payload, expect_reply=True
             )
+        elif command_id == 0x0002:
+            cmd_payload.data = [
+                4,
+                100 - position,
+            ]  # remap the command to the Tuya command
+
+            return self.endpoint.tuya_manufacturer.command(
+                TUYA_SET_DATA, cmd_payload, expect_reply=True
+            )
+            

         return foundation.Status.UNSUP_CLUSTER_COMMAND

To be honnest, I never used that so I'm not really looking at implementing. Also, I cannot test it at the moment. If you confirm you have something working and I can test, I'll add it to the PR

holdestmade commented 3 years ago

Will this work with this motor, seems very similar to others here ?

{
  "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)",
  "endpoints": {
    "1": {
      "profile_id": 260,
      "device_type": "0x0051",
      "in_clusters": [
        "0x0000",
        "0x0004",
        "0x0005",
        "0x000a",
        "0xef00"
      ],
      "out_clusters": [
        "0x0019"
      ]
    }
  },
  "manufacturer": "_TZE200_3i3exuay",
  "model": "TS0601",
  "class": "zigpy.device.Device"
}
rednus commented 3 years ago

It won't work. the mapping ("TUYA_COVER_COMMAND") is used for all command ids. Command id 2 should get, I believe a 4 bytes data with the percentage in the lowest byte.

@blauret I figured it out..

Below code works perfectly on my moeshouse roller.. test your device..

class TuyaWindowCoverControl(LocalDataCluster, WindowCovering):
    """Manufacturer Specific Cluster of Device cover."""

    def __init__(self, *args, **kwargs):
        """Initialize instance."""
        super().__init__(*args, **kwargs)
        self.endpoint.device.cover_bus.add_listener(self)

    def cover_event(self, command, value):
        """Cover event. update the position."""
        self._update_attribute(ATTR_COVER_POSITION, 100 - value[4])

    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.info( "--------------- Sending Tuya Cluster Command..." )
        _LOGGER.info( "--------------- Cluster Command is %x", command_id)
        _LOGGER.info( "--------------- Arugments are %s", args)
        if command_id in (0x0000, 0x0001, 0x0002):
            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,
                TUYA_COVER_COMMAND[command_id],
            ]  # remap the command to the Tuya command
            _LOGGER.info( "--------------- Payload Tuya Command is %x", cmd_payload.command_id)
            return self.endpoint.tuya_manufacturer.command(
                TUYA_SET_DATA, cmd_payload, expect_reply=True
            )

        elif command_id == 0x0005:
            cmd_payload = TuyaManufCluster.Command()
            cmd_payload.status = 0
            cmd_payload.tsn = 0
            cmd_payload.command_id = 0x02
            cmd_payload.function = 0
            position = args[0]
            cmd_payload.data = [
                4, 0, 0,
                100 - position,
            ]  

            _LOGGER.info( "--------------- Payload Tuya Command is %x", cmd_payload.command_id)
            _LOGGER.info( "--------------- Payload data is %s", cmd_payload.data)

            return self.endpoint.tuya_manufacturer.command(
                TUYA_SET_DATA, cmd_payload, expect_reply=True
            )
        elif command_id == 0x0006:
            cmd_payload = TuyaManufCluster.Command()
            cmd_payload.status = args[0]
            cmd_payload.tsn = args[1]
            cmd_payload.command_id = args[2]
            cmd_payload.function = args[3]
            cmd_payload.data = args[4]

            _LOGGER.info( "--------------- Sending Custom Command ----------------------------------")

            return self.endpoint.tuya_manufacturer.command(
                TUYA_SET_DATA, cmd_payload, expect_reply=True
            )
        return foundation.Status.UNSUP_CLUSTER_COMMAND

The last one, 0x0006 you can use to debug from Dev Tools -> Services -> zha.issue_cluser_command with below data. This is useful rather than changing the code everytime to see if it works or not..

service: zha.issue_zigbee_cluster_command
data:
  ieee: 'xx:xx:xx:xx:xx'
  endpoint_id: 1
  cluster_id: 258
  cluster_type: in
  command_type: server
  command: 6
  args:
    - 0
    - 0
    - 2
    - 0
    - - 4
      - 0
      - 0
      - 0
      - 90
blauret commented 3 years ago

@rednus, I updated the PR

rednus commented 3 years ago

@rednus, I updated the PR

Did it work for you?

blauret commented 3 years ago

I cannot try. Building work taking place the moment and the covers are off the wall. The code is pretty straight forward though.

blauret commented 3 years ago

although...

Aren't you missing a 0 here:

       cmd_payload.data = [
            4, 0, 0,
            100 - position,
        ]  
rednus commented 3 years ago

Thanks @rednus, I have the same model and confirm that this works for me too. (Even the buttons are correct!)

@Daniel-Sanche, @alekslyse, @hertznsk Could you please test the new code with yours?

@blauret - Sorry for being pain.. could you please change _LOGGER.info to _LOGGER.debug in the code - dont want to flood the logs with info which might not be needed for everyone..

rednus commented 3 years ago

although...

Aren't you missing a 0 here:

       cmd_payload.data = [
            4, 0, 0,
            100 - position,
        ]  

No.. the data array needs to have 4 values.. [4, 0, 0, 0, position]

Thx

blauret commented 3 years ago

although... Aren't you missing a 0 here:

       cmd_payload.data = [
            4, 0, 0,
            100 - position,
        ]  

No.. the data array needs to have 4 values.. [4, 0, 0, 0, position]

Thx

Right now this is what's in the PR:

        cmd_payload.data = [
            4,
            0,
            0,
            100 - position,
        ]

I don't think it matches.

hertznsk commented 3 years ago

although... Aren't you missing a 0 here:

       cmd_payload.data = [
            4, 0, 0,
            100 - position,
        ]  

Thank you for your work! I added the lost 0 and this works for me, the opening percentage works correctly. But I still have to change the TUYA_COVER_COMMAND mapping.

blauret commented 3 years ago

Thank you for your work! This works for me, the opening percentage works correctly. But I still have to change the TUYA_COVER_COMMAND mapping.

Isn't it up/down inverted for you? In that case you should be able to is the command 6 to send a request to invert in the motor side.

rednus commented 3 years ago

although... Aren't you missing a 0 here:

       cmd_payload.data = [
            4, 0, 0,
            100 - position,
        ]  

No.. the data array needs to have 4 values.. [4, 0, 0, 0, position] Thx

Right now this is what's in the PR:

        cmd_payload.data = [
            4,
            0,
            0,
            100 - position,
        ]

I don't think it matches.

@blauret you are right.. please change it to

cmd_payload.data = [ 4, 0, 0, 0, 100 - position, ]

blauret commented 3 years ago

although... Aren't you missing a 0 here:

       cmd_payload.data = [
            4, 0, 0,
            100 - position,
        ]  

No.. the data array needs to have 4 values.. [4, 0, 0, 0, position] Thx

Right now this is what's in the PR:

        cmd_payload.data = [
            4,
            0,
            0,
            100 - position,
        ]

I don't think it matches.

@blauret you are right.. please change it to

cmd_payload.data = [ 4, 0, 0, 0, 100 - position, ]

I'm feeling stupid now, but it's only 2 zeros...

blauret commented 3 years ago

ok, consider it done.

hertznsk commented 3 years ago

I'm sorry I didn't tell you right away. I added the lost 0 - that's the only way it works. You're right.

rednus commented 3 years ago

I'm feeling stupid now, but it's only 2 zeros...

Sorry.. I am being stupid.. not you.. just couldn't count the zeros.. :(

rednus commented 3 years ago

ok, consider it done.

You are a star 💯

blauret commented 3 years ago

Thank you for your work! This works for me, the opening percentage works

Thanks to @rednus trick, You should be to send from HA:

service: zha.issue_zigbee_cluster_command data: ieee: 'xx:xx:xx:xx:xx' endpoint_id: 1 cluster_id: 258 cluster_type: in command_type: server command: 6 args:

If that does not invert the motor try:

service: zha.issue_zigbee_cluster_command data: ieee: 'xx:xx:xx:xx:xx' endpoint_id: 1 cluster_id: 258 cluster_type: in command_type: server command: 6 args:

blauret commented 3 years ago

ok, consider it done.

You are a star 💯

I'll be glad when it's merged and nobody ask for more change :)

tube0013 commented 3 years ago

TUYA_COVER_COMMAND = {0x0000: 0x0001, 0x0001: 0x0002, 0x0002: 0x0000}

I can confirm this cover mapping works with my Moes ("_TZE200_rddyvrci", "TS0601").. which I added to line 130 in the quirk file to matche it:

        MODELS_INFO: [("_TZE200_zah67ekd", "TS0601"),("_TZE200_rddyvrci", "TS0601")],

Position slider does not work.

rednus commented 3 years ago

00_zah67ekd", "TS0601"),("_TZE200_rdd

@tube0013 - there was an error in the command data did you update your code to reflect that?

cmd_payload.data = [ 4, 0, 0, 0, 100 - position, ]

rednus commented 3 years ago

Guys.. 1 stupid question..

Do I have to restart HA everytime I change the code.. say init.py or is there another way to get this change in?

tube0013 commented 3 years ago

I pulled the most recent commit, and double checked it was in there.

yes restart needed as quirks are read at start up.

rednus commented 3 years ago

I pulled the most recent commit, and double checked it was in there.

So did it work? the latest code? If not can you try various options with service: zha.issue_zigbee_cluster_command? Your device might need a different cmd_payload.command_id instead of 0x02!!

yes restart needed as quirks are read at start up.

Thanks for the confirmation..