question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[Device Support Request] Device support for Tuya TS110E

See original GitHub issue

I am not sure if this is a device request, or if the device itself is just not working. Maybe someone can halp?

I ordered two of these in-wall dimmers:

If successfully installed it and paired it with home assistant (I’m using zha) and was able to use the on-off switch right away, but the dimmer slider wouldn’t do anything. So naturally I thought I’d need a quirk. However, I wasn’t able to get the dimmer slider working.

I pasted the device signature below and you can see that the in_clusters already have "0x0006" and "0x0008" which is OnOff and LevelControl. A lot of the other tuya quirks for the dimmers didn’t have those, so I understand that a quirk was necessary. In this case however I was surprised that it wasn’t working right out of the box, because the blakadder.com entry confirmed it working with ZHA. However, the blakadder.com entry lists it as Zigbee ID: "TS110F"; "_TYZB01_qezuin6k", while the one I actually got it Zigbee ID: "TS110E"; "_TZ3210_ngqk6jia". So a small difference, even though the device itself looks exactly as on the pictures on blakadder.com and aliexpress.com.

I came up with the following quirk that I stored in tuya/ts110e.py:

class DimmerSwitchWithNeutral1Gang(TuyaDimmerSwitch):
    """Tuya Dimmer Switch Module With Neutral 1 Gang"""

    signature = {
        MODELS_INFO: [("_TZ3210_ngqk6jia", "TS110E")],
        ENDPOINTS: {
            # <SimpleDescriptor endpoint=1 profile=260 device_type=257
            # input_clusters=[0, 4, 5, 6, 8, 57345]
            # output_clusters=[10, 25]>
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.DIMMABLE_LIGHT,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    OnOff.cluster_id,
                    LevelControl.cluster_id,
                    TuyaZBExternalSwitchTypeCluster.cluster_id,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            242: {
                # <SimpleDescriptor endpoint=242 profile=41440 device_type=97
                # input_clusters=[]
                # output_clusters=[33]
                PROFILE_ID: 41440,
                DEVICE_TYPE: 97,
                INPUT_CLUSTERS: [],
                OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
            },
        },
    }
    replacement = {
        ENDPOINTS: {
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.DIMMABLE_LIGHT,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    OnOff.cluster_id,
                    LevelControl.cluster_id,
                    # TuyaManufacturerClusterOnOff,
                    # TuyaOnOff,
                    # TuyaManufacturerLevelControl,
                    # TuyaLevelControl,
                    TuyaZBExternalSwitchTypeCluster.cluster_id,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            242: {
                PROFILE_ID: 41440,
                DEVICE_TYPE: 97,
                INPUT_CLUSTERS: [],
                OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
            },
        },
    }

The quirk is seen by home assistant (I see the Quirk: zhaquirks.tuya.ts110e.DimmerSwitchWithNeutral1Gang on the device in the home assistant UI). As you can see I experimented with all combinations of the OnOff and LevelControl alternatives. So far only the original

                    OnOff.cluster_id,
                    LevelControl.cluster_id,

works for at least the switch but not the dimmer.
Other combinations i tried:

                    TuyaOnOff,
                    TuyaLevelControl,

that comes with error

2022-03-06 22:48:32 ERROR (MainThread) [homeassistant.components.websocket_api.http.connection] [548252938000] 'DimmerSwitchWithNeutral1Gang' object has no attribute 'command_bus'

Which makes sense. So I tried

                    TuyaManufacturerClusterOnOff,
                    TuyaOnOff,
                    TuyaManufacturerLevelControl,
                    TuyaLevelControl,

Which didn’t throw an error and allowed me to call the switch in the UI of home assistant, but it wouldn’t turn on the light and the switch in the UI jumps back like 2 seconds later.

Does anyone have any other suggestions of what I could try?

Device signature:

{
  "node_descriptor": "NodeDescriptor(logical_type=<LogicalType.Router: 1>, complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=<FrequencyBand.Freq2400MHz: 8>, mac_capability_flags=<MACCapabilityFlags.AllocateAddress|RxOnWhenIdle|MainsPowered|FullFunctionDevice: 142>, manufacturer_code=4417, maximum_buffer_size=66, maximum_incoming_transfer_size=66, server_mask=10752, maximum_outgoing_transfer_size=66, descriptor_capability_field=<DescriptorCapability.NONE: 0>, *allocate_address=True, *is_alternate_pan_coordinator=False, *is_coordinator=False, *is_end_device=False, *is_full_function_device=True, *is_mains_powered=True, *is_receiver_on_when_idle=True, *is_router=True, *is_security_capable=False)",
  "endpoints": {
    "1": {
      "profile_id": 260,
      "device_type": "0x0101",
      "in_clusters": [
        "0x0000",
        "0x0004",
        "0x0005",
        "0x0006",
        "0x0008",
        "0xe001"
      ],
      "out_clusters": [
        "0x000a",
        "0x0019"
      ]
    },
    "242": {
      "profile_id": 41440,
      "device_type": "0x0061",
      "in_clusters": [],
      "out_clusters": [
        "0x0021"
      ]
    }
  },
  "manufacturer": "_TZ3210_ngqk6jia",
  "model": "TS110E",
  "class": "zhaquirks.tuya.ts110e.DimmerSwitchWithNeutral1Gang"
}

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

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:46 (24 by maintainers)

github_iconTop GitHub Comments

5reactions
jonnylangefeldcommented, Mar 9, 2022

Wow! Good news for when you wake up 😄 After a few more changes it’s finally working 🎉! Thank you so much!!

I had to move the class TuyaLevelPayload outside the class because otherwise it would say NameError: name 'TuyaLevelPayload' is not defined. I also had to name the parameters manufacturer, expect_reply and tsn cause otherwise they would count into the *args of the command method. But with that it’s finally working now!

Here’s the final product:

class TuyaLevelPayload(t.Struct):
    """Tuya Level payload."""

    level: t.uint16_t
    transtime: t.uint16_t


class F000LevelControlCluster(LevelControl):
    """LevelControlCluster that reports to attrid 0xF000."""

    manufacturer_attributes = {0xF000: ("manufacturer_current_level", t.uint16_t),} 

    manufacturer_server_commands = {
        0x00F0: ("moveToLevelTuya", (TuyaLevelPayload,), False),
    }

    # 0xF000 reported values are 0-1000, convert to 0-255
    def _update_attribute(self, attrid, value):
        if attrid == 0xF000:
            value = (value * 255) // 1000
            attrid = 0x0000

        super()._update_attribute(attrid, value)

    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,
    ):
        """Override the default Cluster command."""
        self.debug(
            "Sending Cluster Command. Cluster Command is %x, Arguments are %s",
            command_id,
            args,
        )
        # move_to_level, move, move_to_level_with_on_off
        if command_id in (0x0000, 0x0001, 0x0004):
            # convert dim values to 0-1000
            brightness = (args[0] * 1000) // 255
            return await super().command(
                0x00F0, 
                TuyaLevelPayload(level=brightness, transtime=0), 
                manufacturer=manufacturer, 
                expect_reply=expect_reply,
                tsn=tsn
            )

        return super().command(command_id, *args, manufacturer, expect_reply, tsn)

I will clean up the imports, format with black and whip up a PR.

Also, do you have a bitcoin lightning address? I’d like to send you a tip! This was amazing help!

2reactions
javicallecommented, Mar 9, 2022

I’m also reading the same posts.

Today last offer:

  class F000LevelControlCluster(LevelControl):
    """LevelControlCluster that reports to attrid 0xF000."""

    class TuyaLevelPayload(t.Struct):
        """Tuya Level payload."""

        level: t.uint16_t
        transtime: t.uint16_t

    manufacturer_attributes = {0xF000: ("manufacturer_current_level", t.uint16_t),} 

    manufacturer_server_commands = {
        0x00F0: ("moveToLevelTuya", (TuyaLevelPayload,), False),
    }

    # 0xF000 reported values are 0-1000, convert to 0-255
    def _update_attribute(self, attrid, value):
        if attrid == 0xF000:
            value = (value * 255) // 1000
            attrid = 0x0000

        super()._update_attribute(attrid, value)

    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,
    ):
        """Override the default Cluster command."""
        self.debug(
            "Sending Cluster Command. Cluster Command is %x, Arguments are %s",
            command_id,
            args,
        )
        # move_to_level, move, move_to_level_with_on_off
        if command_id in (0x0000, 0x0001, 0x0004):
            # convert dim values to 0-1000
            brightness = (args[0] * 1000) // 255
            return await super().command(
                0x00F0, 
                TuyaLevelPayload(level=brightness, transtime=0), 
                manufacturer, 
                expect_reply,
                tsn
            )

        return super().command(command_id, *args, manufacturer, expect_reply, tsn)

Not sure if it must be manufacturer_server_commands or manufacturer_client_commands. Try to change if gives you problems.

I will check the results in (my) morning. 😴

Read more comments on GitHub >

github_iconTop Results From Across the Web

Zigbee Connectivity Specifications
To connect a Zigbee device to the Tuya IoT Development Platform and mobile apps, your development must follow a set of end-to-end ...
Read more >
Driver Support for Zigbee Model TS110E ...
I have seen community based drivers for this device but they don't seem to support the model ending in E. I can turn...
Read more >
[APP][Pro] Tuya Zigbee App - Apps
This app makes Homey support Tuya Zigbee devices! ... not supported by the app please leave a comment with the information requested below....
Read more >
Support new TuYa devices
Adding support for TuYa devices is a bit different. In order to provide support ... const fz = require('zigbee-herdsman-converters/converters/fromZigbee'); ...
Read more >
Device support
Avatto. Model, Description. MIUCOT10Z, Avatto Smart plug (with power monitoring by polling) (white-label of TuYa TS011F_plug_3) ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found