Curtain Switch TS130F doesn't work correctly, no calibration

Hello,

I’m a complete newby to Home Assistant but I know it’s time change to a correct Smart Home!

I bough 4 curtain switches on Amazon. Here is the 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.FullFunctionDevice|MainsPowered|RxOnWhenIdle|AllocateAddress: 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": "0x0104",
      "device_type": "0x0202",
      "input_clusters": [
        "0x0000",
        "0x0003",
        "0x0004",
        "0x0005",
        "0x0006",
        "0x0102"
      ],
      "output_clusters": [
        "0x000a"
      ]
    }
  },
  "manufacturer": "_TZ3000_ltiqubue",
  "model": "TS130F",
  "class": "zigpy.device.Device"
}

I tried a lot since a week to get them working properly but I didn’t manage it. I Installed ZHA Toolkit, activated it in the configuration.yaml by adding:

zha_toolkit:

zha:
  enable_quirks: true
  custom_quirks_path: /config/zha_quirks/

I used the the TS130F.py file and I have the following lines:

"""Device handler for loratap TS130F smart curtain switch."""

from zigpy.profiles import zha

from zigpy.quirks import CustomCluster, CustomDevice

import zigpy.types as t

from zigpy.zcl.clusters.closures import WindowCovering

from zigpy.zcl.clusters.general import Basic, Groups, OnOff, Ota, Scenes, Time

from zhaquirks.const import (

    DEVICE_TYPE,

    ENDPOINTS,

    INPUT_CLUSTERS,

    MODELS_INFO,

    OUTPUT_CLUSTERS,

    PROFILE_ID,

)

ATTR_CURRENT_POSITION_LIFT_PERCENTAGE = 0x0008

CMD_GO_TO_LIFT_PERCENTAGE = 0x0005

class TuyaWithBacklightOnOffCluster(CustomCluster):

    """TuyaSmartCurtainOnOffCluster: fire events corresponding to press type."""

    cluster_id = OnOff.cluster_id

    LIGHT_MODE_1 = {0x8001: 0}

    LIGHT_MODE_2 = {0x8001: 1}

    LIGHT_MODE_3 = {0x8001: 2}

    attributes = {0x8001: ("backlight_mode", t.enum8)}

class TuyaCoveringCluster(CustomCluster, WindowCovering):

    """TuyaSmartCurtainWindowCoveringCluster: Allow to setup Window covering tuya devices."""

    attributes = WindowCovering.attributes.copy()

    attributes.update({0xF000: ("tuya_moving_state", t.enum8)})

    attributes.update({0xF001: ("calibration", t.enum8)})

    attributes.update({0xF002: ("motor_reversal", t.enum8)})

    def _update_attribute(self, attrid, value):

        if attrid == ATTR_CURRENT_POSITION_LIFT_PERCENTAGE:

            # Invert the percentage value (cf https://github.com/dresden-elektronik/deconz-rest-plugin/issues/3757)

            value = 100 - value

        super()._update_attribute(attrid, value)

    async def command(

        self, command_id, *args, manufacturer=None, expect_reply=True, tsn=None

    ):

        """Override default command to invert percent lift value."""

        if command_id == CMD_GO_TO_LIFT_PERCENTAGE:

            percent = args[0]

            # Invert the percentage value (cf https://github.com/dresden-elektronik/deconz-rest-plugin/issues/3757)

            percent = 100 - percent

            v = (percent,)

            return await super().command(command_id, *v)

        return await super().command(

            command_id,

            *args,

            manufacturer=manufacturer,

            expect_reply=expect_reply,

            tsn=tsn

        )

class TuyaTS130F(CustomDevice):

    """Tuya smart curtain roller shutter."""

    signature = {

        # SizePrefixedSimpleDescriptor(endpoint=1, profile=260, device_type=0x0202, device_version=1, input_clusters=[0, 4, 5, 6, 10, 0x0102], output_clusters=[25]))

        MODELS_INFO: [

            ("_TZ3000_8kzqqzu4", "TS130F"),

            ("_TZ3000_egq7y6pr", "TS130F"),

        ],

        ENDPOINTS: {

            1: {

                PROFILE_ID: zha.PROFILE_ID,

                DEVICE_TYPE: zha.DeviceType.WINDOW_COVERING_DEVICE,

                INPUT_CLUSTERS: [

                    Basic.cluster_id,

                    Groups.cluster_id,

                    Scenes.cluster_id,

                    Time.cluster_id,

                    OnOff.cluster_id,

                    WindowCovering.cluster_id,

                ],

                OUTPUT_CLUSTERS: [Ota.cluster_id],

            },

        },

    }

    replacement = {

        ENDPOINTS: {

            1: {

                PROFILE_ID: zha.PROFILE_ID,

                DEVICE_TYPE: zha.DeviceType.WINDOW_COVERING_DEVICE,

                INPUT_CLUSTERS: [

                    Basic.cluster_id,

                    Groups.cluster_id,

                    Scenes.cluster_id,

                    Time.cluster_id,

                    TuyaWithBacklightOnOffCluster,

                    TuyaCoveringCluster,

                ],

                OUTPUT_CLUSTERS: [Ota.cluster_id],

            },

        },

    }

class TuyaZemismartTS130F(CustomDevice):

    """Tuya ZemiSmart smart curtain roller shutter."""

    signature = {

        # SizePrefixedSimpleDescriptor(endpoint=1, profile=260, device_type=0x0202, device_version=1, input_clusters=[0x0000, 0x0004, 0x0005, 0x0006, 0x0102], output_clusters=[0x000a, 0x0019]))

        MODELS_INFO: [("_TZ3000_ltiqubue", "TS130F")],

        ENDPOINTS: {

            1: {

                PROFILE_ID: zha.PROFILE_ID,

                DEVICE_TYPE: zha.DeviceType.WINDOW_COVERING_DEVICE,

                INPUT_CLUSTERS: [

                    Basic.cluster_id,

                    Groups.cluster_id,

                    Scenes.cluster_id,

                    OnOff.cluster_id,

                    WindowCovering.cluster_id,

                ],

                OUTPUT_CLUSTERS: [

                    Time.cluster_id,

                    Ota.cluster_id,

                ],

            },

        },

    }

    replacement = {

        ENDPOINTS: {

            1: {

                PROFILE_ID: zha.PROFILE_ID,

                DEVICE_TYPE: zha.DeviceType.WINDOW_COVERING_DEVICE,

                INPUT_CLUSTERS: [

                    Basic.cluster_id,

                    Groups.cluster_id,

                    Scenes.cluster_id,

                    TuyaWithBacklightOnOffCluster,

                    TuyaCoveringCluster,

                ],

                OUTPUT_CLUSTERS: [

                    Time.cluster_id,

                    Ota.cluster_id,

                ],

            },

        },

    }

Can somebody tell me why the quirk isn’t used?

I cannot calibrate the endpoints of the switch so the curtain doesn’t close completely. Option calibration is not present. Also open and close is reversed.

It would be very nice is somebody could tell me what I am doing wrong and If there is a way to get those switches working properly as I need to install 15 of them…

Thanks in advance!

That question is best asked and answered in a new device feature request issue to the ZHA Device Handlers repository. Read here about posting a new device feature request issue to the ZHA Device Handlers repository → https://www.home-assistant.io/integrations/zha#how-to-add-support-for-new-and-unsupported-devices