ZHA new device support request

Hello.

I found unsupported device due a new integration activity, it is 2-channel Zigbee Dimmer module BXBO-XV-BD.

Diagnostics:
{
  "home_assistant": {
    "installation_type": "Home Assistant Container",
    "version": "2025.9.3",
    "dev": false,
    "hassio": false,
    "virtualenv": false,
    "python_version": "3.13.7",
    "docker": true,
    "arch": "aarch64",
    "timezone": "Europe/Berlin",
    "os_name": "Linux",
    "os_version": "6.12.34+rpt-rpi-v8",
    "container_arch": "aarch64",
    "run_as_root": true
  },
  "custom_components": {},
  "integration_manifest": {
    "domain": "zha",
    "name": "Zigbee Home Automation",
    "after_dependencies": [
      "hassio",
      "onboarding",
      "usb"
    ],
    "codeowners": [
      "dmulcahey",
      "adminiuga",
      "puddly",
      "TheJulianJES"
    ],
    "config_flow": true,
    "dependencies": [
      "file_upload",
      "homeassistant_hardware"
    ],
    "documentation": "https://www.home-assistant.io/integrations/zha",
    "iot_class": "local_polling",
    "loggers": [
      "aiosqlite",
      "bellows",
      "crccheck",
      "pure_pcapy3",
      "zhaquirks",
      "zigpy",
      "zigpy_deconz",
      "zigpy_xbee",
      "zigpy_zigate",
      "zigpy_znp",
      "zha",
      "universal_silabs_flasher"
    ],
    "requirements": [
      "zha==0.0.70"
    ],
    "usb": [
      {
        "vid": "10C4",
        "pid": "EA60",
        "description": "*2652*",
        "known_devices": [
          "slae.sh cc2652rb stick"
        ]
      },
      {
        "vid": "10C4",
        "pid": "EA60",
        "description": "*slzb-07*",
        "known_devices": [
          "smlight slzb-07"
        ]
      },
      {
        "vid": "1A86",
        "pid": "55D4",
        "description": "*sonoff*plus*",
        "known_devices": [
          "sonoff zigbee dongle plus v2"
        ]
      },
      {
        "vid": "10C4",
        "pid": "EA60",
        "description": "*sonoff*plus*",
        "known_devices": [
          "sonoff zigbee dongle plus"
        ]
      },
      {
        "vid": "10C4",
        "pid": "EA60",
        "description": "*tubeszb*",
        "known_devices": [
          "TubesZB Coordinator"
        ]
      },
      {
        "vid": "1A86",
        "pid": "7523",
        "description": "*tubeszb*",
        "known_devices": [
          "TubesZB Coordinator"
        ]
      },
      {
        "vid": "1A86",
        "pid": "7523",
        "description": "*zigstar*",
        "known_devices": [
          "ZigStar Coordinators"
        ]
      },
      {
        "vid": "1CF1",
        "pid": "0030",
        "description": "*conbee*",
        "known_devices": [
          "Conbee II"
        ]
      },
      {
        "vid": "0403",
        "pid": "6015",
        "description": "*conbee*",
        "known_devices": [
          "Conbee III"
        ]
      },
      {
        "vid": "10C4",
        "pid": "8A2A",
        "description": "*zigbee*",
        "known_devices": [
          "Nortek HUSBZB-1"
        ]
      },
      {
        "vid": "0403",
        "pid": "6015",
        "description": "*zigate*",
        "known_devices": [
          "ZiGate+"
        ]
      },
      {
        "vid": "10C4",
        "pid": "EA60",
        "description": "*zigate*",
        "known_devices": [
          "ZiGate"
        ]
      },
      {
        "vid": "10C4",
        "pid": "8B34",
        "description": "*bv 2010/10*",
        "known_devices": [
          "Bitron Video AV2010/10"
        ]
      },
      {
        "vid": "10C4",
        "pid": "EA60",
        "description": "*sonoff*max*",
        "known_devices": [
          "SONOFF Dongle Max MG24"
        ]
      },
      {
        "vid": "10C4",
        "pid": "EA60",
        "description": "*sonoff*lite*mg21*",
        "known_devices": [
          "sonoff zigbee dongle lite mg21"
        ]
      }
    ],
    "zeroconf": [
      {
        "type": "_esphomelib._tcp.local.",
        "name": "tube*"
      },
      {
        "type": "_zigate-zigbee-gateway._tcp.local.",
        "name": "*zigate*"
      },
      {
        "type": "_zigstar_gw._tcp.local.",
        "name": "*zigstar*"
      },
      {
        "type": "_uzg-01._tcp.local.",
        "name": "uzg-01*"
      },
      {
        "type": "_slzb-06._tcp.local.",
        "name": "slzb-06*"
      },
      {
        "type": "_xzg._tcp.local.",
        "name": "xzg*"
      },
      {
        "type": "_czc._tcp.local.",
        "name": "czc*"
      },
      {
        "type": "_zigbee-coordinator._tcp.local.",
        "name": "*"
      }
    ],
    "is_built_in": true,
    "overwrites_built_in": false
  },
  "setup_times": {
    "null": {
      "setup": 0.0001685169991105795
    },
    "1a32f0c576f508e5f6cc6076e13f3019": {
      "wait_import_platforms": -0.05884899606462568,
      "wait_base_component": -0.0019841589964926243,
      "config_entry_setup": 9.401092334999703
    }
  },
  "data": {
    "version": 1,
    "ieee": "**REDACTED**",
    "nwk": "0x7175",
    "manufacturer": "_TZE284_jtbgusdc",
    "model": "TS0601",
    "friendly_manufacturer": "_TZE284_jtbgusdc",
    "friendly_model": "TS0601",
    "name": "_TZE284_jtbgusdc TS0601",
    "quirk_applied": false,
    "quirk_class": "zigpy.device.Device",
    "quirk_id": null,
    "manufacturer_code": 4417,
    "power_source": "Mains",
    "lqi": 80,
    "rssi": -80,
    "last_seen": "2025-09-18T14:18:35.025018+00:00",
    "available": true,
    "device_type": "Router",
    "active_coordinator": false,
    "node_descriptor": {
      "logical_type": "Router",
      "complex_descriptor_available": false,
      "user_descriptor_available": false,
      "reserved": 0,
      "aps_flags": 0,
      "frequency_band": 8,
      "mac_capability_flags": 142,
      "manufacturer_code": 4417,
      "maximum_buffer_size": 66,
      "maximum_incoming_transfer_size": 66,
      "server_mask": 10752,
      "maximum_outgoing_transfer_size": 66,
      "descriptor_capability_field": 0
    },
    "endpoints": {
      "1": {
        "profile_id": 260,
        "device_type": {
          "name": "SMART_PLUG",
          "id": 81
        },
        "in_clusters": [
          {
            "cluster_id": "0x0000",
            "endpoint_attribute": "basic",
            "attributes": [
              {
                "id": "0x0001",
                "name": "app_version",
                "zcl_type": "uint8",
                "value": 78
              },
              {
                "id": "0x0004",
                "name": "manufacturer",
                "zcl_type": "string",
                "value": "_TZE284_jtbgusdc"
              },
              {
                "id": "0x0005",
                "name": "model",
                "zcl_type": "string",
                "value": "TS0601"
              }
            ]
          },
          {
            "cluster_id": "0x0004",
            "endpoint_attribute": "groups",
            "attributes": []
          },
          {
            "cluster_id": "0x0005",
            "endpoint_attribute": "scenes",
            "attributes": []
          },
          {
            "cluster_id": "0xed00",
            "endpoint_attribute": null,
            "attributes": []
          },
          {
            "cluster_id": "0xef00",
            "endpoint_attribute": null,
            "attributes": []
          }
        ],
        "out_clusters": [
          {
            "cluster_id": "0x000a",
            "endpoint_attribute": "time",
            "attributes": []
          },
          {
            "cluster_id": "0x0019",
            "endpoint_attribute": "ota",
            "attributes": [
              {
                "id": "0x0002",
                "name": "current_file_version",
                "zcl_type": "uint32",
                "value": 78
              }
            ]
          }
        ]
      },
      "242": {
        "profile_id": 41440,
        "device_type": {
          "name": "PROXY_BASIC",
          "id": 97
        },
        "in_clusters": [],
        "out_clusters": [
          {
            "cluster_id": "0x0021",
            "endpoint_attribute": "green_power",
            "attributes": []
          }
        ]
      }
    },
    "zha_lib_entities": {
      "sensor": [
        {
          "info_object": {
            "fallback_name": null,
            "unique_id": "**REDACTED**",
            "migrate_unique_ids": [],
            "platform": "sensor",
            "class_name": "LQISensor",
            "translation_key": "lqi",
            "device_class": null,
            "state_class": "measurement",
            "entity_category": "diagnostic",
            "entity_registry_enabled_default": false,
            "enabled": true,
            "primary": false,
            "cluster_handlers": [
              {
                "class_name": "BasicClusterHandler",
                "generic_id": "cluster_handler_0x0000",
                "endpoint_id": 1,
                "cluster": {
                  "id": 0,
                  "name": "Basic",
                  "type": "server"
                },
                "id": "1:0x0000",
                "unique_id": "**REDACTED**",
                "status": "INITIALIZED",
                "value_attribute": null
              }
            ],
            "device_ieee": "**REDACTED**",
            "endpoint_id": 1,
            "available": true,
            "group_id": null,
            "suggested_display_precision": null,
            "unit": null
          },
          "state": {
            "class_name": "LQISensor",
            "available": true,
            "state": 80
          }
        },
        {
          "info_object": {
            "fallback_name": null,
            "unique_id": "**REDACTED**",
            "migrate_unique_ids": [],
            "platform": "sensor",
            "class_name": "RSSISensor",
            "translation_key": "rssi",
            "device_class": "signal_strength",
            "state_class": "measurement",
            "entity_category": "diagnostic",
            "entity_registry_enabled_default": false,
            "enabled": true,
            "primary": false,
            "cluster_handlers": [
              {
                "class_name": "BasicClusterHandler",
                "generic_id": "cluster_handler_0x0000",
                "endpoint_id": 1,
                "cluster": {
                  "id": 0,
                  "name": "Basic",
                  "type": "server"
                },
                "id": "1:0x0000",
                "unique_id": "**REDACTED**",
                "status": "INITIALIZED",
                "value_attribute": null
              }
            ],
            "device_ieee": "**REDACTED**",
            "endpoint_id": 1,
            "available": true,
            "group_id": null,
            "suggested_display_precision": null,
            "unit": "dBm"
          },
          "state": {
            "class_name": "RSSISensor",
            "available": true,
            "state": -80
          }
        }
      ],
      "update": [
        {
          "info_object": {
            "fallback_name": null,
            "unique_id": "**REDACTED**",
            "migrate_unique_ids": [],
            "platform": "update",
            "class_name": "FirmwareUpdateEntity",
            "translation_key": null,
            "device_class": "firmware",
            "state_class": null,
            "entity_category": "config",
            "entity_registry_enabled_default": true,
            "enabled": true,
            "primary": false,
            "cluster_handlers": [
              {
                "class_name": "OtaClientClusterHandler",
                "generic_id": "cluster_handler_0x0019_client",
                "endpoint_id": 1,
                "cluster": {
                  "id": 25,
                  "name": "Ota",
                  "type": "client"
                },
                "id": "1:0x0019_client",
                "unique_id": "**REDACTED**",
                "status": "INITIALIZED",
                "value_attribute": null
              }
            ],
            "device_ieee": "**REDACTED**",
            "endpoint_id": 1,
            "available": true,
            "group_id": null,
            "supported_features": 7
          },
          "state": {
            "class_name": "FirmwareUpdateEntity",
            "available": true,
            "installed_version": "0x0000004e",
            "in_progress": false,
            "update_percentage": null,
            "latest_version": null,
            "release_summary": null,
            "release_notes": null,
            "release_url": null
          }
        }
      ]
    },
    "neighbors": [],
    "routes": []
  },
  "issues": []
}

It it possible to add support for this device?

Thank you

Hello kbakhchin,

Look here ZHA Device Handlers

Hello.

Found ID in for (“_TZE204_jtbgusdc”, “TS0601”), # Avatto DMS16/ZDMS16

https://raw.githubusercontent.com/zigpy/zha-device-handlers/2e2bbd980c5e5cd5ea4853a1ac66a6b029b62031/zhaquirks/tuya/ts0601_dimmer.py

Added to Homeassistant quirks directory, nothing happens.

Thank you.

Hello.

DEBUG log:

2025-09-19 08:29:53.631 DEBUG (MainThread) [zigpy.application] Device is initialized <Device model='TS0601' manuf='_TZE284_jtbgusdc' nwk=0xCA42 ieee=a4:c1:38:00:38:62:a3:d3 is_initialized=True>
2025-09-19 08:29:53.633 DEBUG (MainThread) [zha] Emitting event raw_device_initialized with data RawDeviceInitializedEvent(device_info=RawDeviceInitializedDeviceInfo(ieee=a4:c1:38:00:38:62:a3:d3, nwk=0xCA42, pairing_status=<DevicePairingStatus.INTERVIEW_COMPLETE: 2>, model='TS0601', manufacturer='_TZE284_jtbgusdc', signature={'manufacturer': '_TZE284_jtbgusdc', 'model': 'TS0601', 'node_desc': {'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>}, 'endpoints': {1: {'profile_id': 260, 'device_type': <DeviceType.SMART_PLUG: 81>, 'input_clusters': [4, 5, 61184, 0, 60672], 'output_clusters': [25, 10]}, 242: {'profile_id': 41440, 'device_type': 97, 'input_clusters': [], 'output_clusters': [33]}}}), event_type='zha_gateway_message', event='raw_device_initialized') (1 listeners)
2025-09-19 08:29:53.633 DEBUG (MainThread) [zha] (ZHAGatewayProxy) handling event protocol for event: RawDeviceInitializedEvent(device_info=RawDeviceInitializedDeviceInfo(ieee=a4:c1:38:00:38:62:a3:d3, nwk=0xCA42, pairing_status=<DevicePairingStatus.INTERVIEW_COMPLETE: 2>, model='TS0601', manufacturer='_TZE284_jtbgusdc', signature={'manufacturer': '_TZE284_jtbgusdc', 'model': 'TS0601', 'node_desc': {'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>}, 'endpoints': {1: {'profile_id': 260, 'device_type': <DeviceType.SMART_PLUG: 81>, 'input_clusters': [4, 5, 61184, 0, 60672], 'output_clusters': [25, 10]}, 242: {'profile_id': 41440, 'device_type': 97, 'input_clusters': [], 'output_clusters': [33]}}}), event_type='zha_gateway_message', event='raw_device_initialized')
2025-09-19 08:29:53.634 DEBUG (MainThread) [zigpy.quirks.registry] Checking quirks for _TZE284_jtbgusdc TS0601 (a4:c1:38:00:38:62:a3:d3)
2025-09-19 08:29:53.634 DEBUG (MainThread) [zigpy.quirks.registry] Considering <class 'zhaquirks.xbee.xbee_io.XBeeSensor'>
2025-09-19 08:29:53.634 DEBUG (MainThread) [zigpy.quirks] Fail because endpoint list mismatch: {232, 230} {1, 242}
2025-09-19 08:29:53.635 DEBUG (MainThread) [zigpy.quirks.registry] Considering <class 'zhaquirks.xbee.xbee3_io.XBee3Sensor'>
2025-09-19 08:29:53.635 DEBUG (MainThread) [zigpy.quirks] Fail because endpoint list mismatch: {232, 230} {1, 242}
2025-09-19 08:29:53.635 DEBUG (MainThread) [zigpy.quirks.registry] Considering <class 'zhaquirks.tuya.ts0201.MoesTemperatureHumidtySensorWithScreen'>
2025-09-19 08:29:53.635 DEBUG (MainThread) [zigpy.quirks] Fail because endpoint list mismatch: {1} {1, 242}
2025-09-19 08:29:53.636 DEBUG (MainThread) [zigpy.quirks.registry] Considering <class 'zhaquirks.smartthings.tag_v4.SmartThingsTagV4'>
2025-09-19 08:29:53.636 DEBUG (MainThread) [zigpy.quirks] Fail because endpoint list mismatch: {1} {1, 242}
2025-09-19 08:29:53.636 DEBUG (MainThread) [zigpy.quirks.registry] Considering <class 'zhaquirks.smartthings.multi.SmartthingsMultiPurposeSensor'>
2025-09-19 08:29:53.636 DEBUG (MainThread) [zigpy.quirks] Fail because endpoint list mismatch: {1} {1, 242}
2025-09-19 08:29:53.636 DEBUG (MainThread) [zigpy.quirks.registry] Considering <class 'zhaquirks.netvox.z308e3ed.Z308E3ED'>
2025-09-19 08:29:53.637 DEBUG (MainThread) [zigpy.quirks] Fail because endpoint list mismatch: {1} {1, 242}
2025-09-19 08:29:53.637 DEBUG (MainThread) [zigpy.quirks.registry] Considering <class 'zhaquirks.gledopto.soposhgu10.SoposhGU10'>
2025-09-19 08:29:53.637 DEBUG (MainThread) [zigpy.quirks] Fail because endpoint list mismatch: {11, 13} {1, 242}

Thank you.

Hello.

Found ID in [New device support]: Avatto - ZDMS16-Y2 · Issue #28341 · Koenkk/zigbee2mqtt · GitHub

looks like it is fully support with zigbee2mqtt.

Thanks.

Not sure if you solved it already, but the first part of your ID is different. It’s not TZE204, but TZE284.
Furthermore, for me it wasn´t working with the mentioned quirk. Had to change input and outputclusters,device and profile ids like this:



class TuyaGuidoDoubleSwitchDimmerGP(TuyaDimmerSwitch):
    """Tuya double channel dimmer device."""

    signature = {
        MODELS_INFO: [
            ("_TZE284_jtbgusdc", "TS0601"),  # Avatto DMS16/ZDMS16
        ],
        ENDPOINTS: {
            # <SimpleDescriptor endpoint=1 profile=260 device_type=0x0100
            # device_version=1
            # input_clusters=[0, 4, 5, 61184]
            # output_clusters=[10, 25]>
            1: {
                PROFILE_ID: 0x0104,
                DEVICE_TYPE: 0x0051,
                INPUT_CLUSTERS: [
                    0x0000,
                    0x0004,
                    0x0005,
                    0xed00,
                    0xef00
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            # <SimpleDescriptor endpoint=242 profile=41440 device_type=97
            # input_clusters=[]
            # output_clusters=[33]
            242: {
                PROFILE_ID: 0xa1e0,
                DEVICE_TYPE: 0x0061,
                INPUT_CLUSTERS: [],
                OUTPUT_CLUSTERS: [0x0021],
            },
        },
    }

    replacement = {
        ENDPOINTS: {
            1: {
                DEVICE_TYPE: zha.DeviceType.ON_OFF_LIGHT,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    TuyaLevelControlManufCluster,
                    TuyaOnOffNM,
                    TuyaInWallLevelControlNM,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            2: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.ON_OFF_LIGHT,
                INPUT_CLUSTERS: [
                    TuyaOnOffNM,
                    TuyaInWallLevelControlNM,
                ],
                OUTPUT_CLUSTERS: [],
            },
            242: {
                PROFILE_ID: zgp.PROFILE_ID,
                DEVICE_TYPE: zgp.DeviceType.PROXY_BASIC,
                INPUT_CLUSTERS: [],
                OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
            },
        }
    }


2 Likes

Nice, this works!

I did notice that changing the brightness in Home Assistant is not exactly corresponding with the brightness of the lamp. For example, setting brightness to 25% is sometimes very bright, sometimes completely off. Feels like the level control is not fully correct yet?

Quick note to anybody coming from google get their device to work and might not be so familar with coding,
You also need to add imports before the class definition that guid_o gave if you have this as a standalone py-file in your custom quirks folder:



from zigpy.profiles import zha, zgp
from zigpy.zcl.clusters.general import (
    Basic,
    GreenPowerProxy,
    Groups,
    Ota,
    Scenes,
    Time,
)

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

from zhaquirks.tuya.ts0601_dimmer import (
    TuyaDimmerSwitch,
    TuyaInWallLevelControlNM,
)

from zhaquirks.tuya.mcu import (
    TuyaLevelControlManufCluster,
    TuyaOnOffNM,
)

2 Likes

"Thanks for the code! I’m using it now, but I haven’t managed to get the toggle mode (momentary switch) to work for the physical inputs. While it’s possible to switch the latching positions (On/Off) under ‘Manage Zigbee Devices’ → ‘TuyaOnOffNM (Endpoint id: 1, Id: 0x0006)’, using the toggle command fails. I get the following error: ‘Failed to perform the action zha/issue_zigbee_cluster_command. Failed to issue cluster command with status: <Status.UNSUP_CLUSTER_COMMAND: 129>’.

Here is the complete quirk code, including HenrikJeppsson’s imports section."

from zigpy.profiles import zha, zgp
from zigpy.zcl.clusters.general import (
    Basic,
    GreenPowerProxy,
    Groups,
    Ota,
    Scenes,
    Time,
)

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

from zhaquirks.tuya.ts0601_dimmer import (
    TuyaDimmerSwitch,
    TuyaInWallLevelControlNM,
)

from zhaquirks.tuya.mcu import (
    TuyaLevelControlManufCluster,
    TuyaOnOffNM,
)

class TuyaGuidoDoubleSwitchDimmerGP(TuyaDimmerSwitch):
    """Tuya double channel dimmer device."""

    signature = {
        MODELS_INFO: [
            ("_TZE284_jtbgusdc", "TS0601"),  # Avatto DMS16/ZDMS16
        ],
        ENDPOINTS: {
            # <SimpleDescriptor endpoint=1 profile=260 device_type=0x0100
            # device_version=1
            # input_clusters=[0, 4, 5, 61184]
            # output_clusters=[10, 25]>
            1: {
                PROFILE_ID: 0x0104,
                DEVICE_TYPE: 0x0051,
                INPUT_CLUSTERS: [
                    0x0000,
                    0x0004,
                    0x0005,
                    0xed00,
                    0xef00
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            # <SimpleDescriptor endpoint=242 profile=41440 device_type=97
            # input_clusters=[]
            # output_clusters=[33]
            242: {
                PROFILE_ID: 0xa1e0,
                DEVICE_TYPE: 0x0061,
                INPUT_CLUSTERS: [],
                OUTPUT_CLUSTERS: [0x0021],
            },
        },
    }

    replacement = {
        ENDPOINTS: {
            1: {
                DEVICE_TYPE: zha.DeviceType.ON_OFF_LIGHT,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    TuyaLevelControlManufCluster,
                    TuyaOnOffNM,
                    TuyaInWallLevelControlNM,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            2: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.ON_OFF_LIGHT,
                INPUT_CLUSTERS: [
                    TuyaOnOffNM,
                    TuyaInWallLevelControlNM,
                ],
                OUTPUT_CLUSTERS: [],
            },
            242: {
                PROFILE_ID: zgp.PROFILE_ID,
                DEVICE_TYPE: zgp.DeviceType.PROXY_BASIC,
                INPUT_CLUSTERS: [],
                OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
            },
        }
    }

This is whats working so far:

Aces! Well done! What does the switches actually do then? Hold to alter lighting level?
I would have believed that the switch behavior was governed by the firmware, is it customizable?

Hi, the physical switches only toggle the lights on or off; they don’t have a dimming function. You can control dimming through Home Assistant.

Yes, there is a firmware setting that allows you to switch between toggle mode and dimming, but I haven’t been able to get it to work.

Hey everyone, got a gift for you! Here’s a fully working ZHA quirk for the Avatto ZDMS16-Y2 (_TZE284_jtbgusdc). What’s been fixed/added compared to the base TuyaDoubleSwitchDimmerGP:

  • Fixed brightness scaling — uses the correct 0-254 range, so setting 100% actually stays at 100% instead of dropping to 99%
  • Added writable Switch Type support (TuyaAttributesCluster) — you can now switch between toggle, state, and momentary modes per channel
  • Added writable Power On Behavior — choose between off, on, or previous (restore last state)
  • Added maximum_level attribute — the base quirk doesn’t expose it, which is why brightness often appears capped at ~50%. You can now read/write it via Manage Clusters → level cluster

See the top of the file for a full usage guide.

If you’d like to buy me a coffee :coffee: → ETH: 0x00bD1072565D923ee224bF7e0d5CC843Ab83DddD

"""Avatto ZDMS16-Y2 (_TZE284_jtbgusdc) double channel dimmer for ZHA.

================================================================================
  INSTALLATION
================================================================================

1. In configuration.yaml:

    zha:
      custom_quirks_path: /config/custom_zha_quirks

2. Copy this file to /config/custom_zha_quirks/ts0601_dimmer_zdms16y2.py
3. Restart Home Assistant
4. Remove the device in ZHA and re-pair it (or restart the ZHA integration)

================================================================================
  WHAT THIS QUIRK EXPOSES
================================================================================

After pairing, the device appears as TWO light entities (Channel 1 + Channel 2),
each with on/off and brightness slider.

Additional configuration is available via:
  ZHA → Device → Manage Zigbee Device → Clusters

================================================================================
  CONFIGURABLE ATTRIBUTES (via Manage Clusters)
================================================================================

CLUSTER: "Tuya Switch Type" (0xFC00)
  Attribute: switch_type
  Values:   "toggle"    — standard toggle switch (default)
            "state"     — latching push button
            "momentary" — momentary / impulse push button (no latch)

  ➜ Each endpoint (ch1, ch2) has its own switch_type.
  ➜ Write the VALUE NAME, not the number (e.g. "momentary", not "2").

CLUSTER: "Tuya Power On Behavior" (0xFC01)
  Attribute: power_on_behavior
  Values:   "off"       — turn off after power restore
            "on"        — turn on after power restore
            "previous"  — restore last state before power loss

  ➜ Global setting, applies to both channels.

CLUSTER: "level" (TuyaInWallLevelControlNM)
  Attribute: minimum_level (0xEF01)
  Type:      uint32, ZCL scale 0-254
  Default:   device-dependent (often ~25, i.e. ~10%)
  ➜ Sets the minimum brightness the dimmer will output.
  ➜ Example: write 10 for ~4% minimum.

  Attribute: maximum_level (0xEF03)
  Type:      uint32, ZCL scale 0-254
  Default:   device-dependent — MAY BE SET LOW FROM FACTORY!
  ➜ If brightness seems capped at ~50%, read this value — it's probably ~127.
  ➜ Write 254 for full 100% range.

  ⚠️  If the brightness slider seems to do nothing above 50%, set maximum_level
      to 254 on BOTH channels. This is the most common issue with ZDMS16.

================================================================================
  TUYA DATAPOINT MAPPING (from Z2M avatto.ts)
================================================================================

    DP 1:  state ch1          (bool)     → on/off
    DP 2:  brightness ch1     (0-1000)   → ZCL current_level (0-254)
    DP 3:  min_brightness ch1 (0-1000)   → ZCL minimum_level (0-254)
    DP 4:  switch_type ch1    (enum)     → toggle=0, state=1, momentary=2
    DP 5:  max_brightness ch1 (0-1000)   → ZCL maximum_level (0-254)
    DP 7:  state ch2          (bool)     → on/off
    DP 8:  brightness ch2     (0-1000)   → ZCL current_level (0-254)
    DP 9:  min_brightness ch2 (0-1000)   → ZCL minimum_level (0-254)
    DP 10: switch_type ch2    (enum)     → toggle=0, state=1, momentary=2
    DP 11: max_brightness ch2 (0-1000)   → ZCL maximum_level (0-254)
    DP 14: power_on_behavior  (enum)     → off=0, on=1, previous=2

================================================================================
  TECHNICAL NOTES
================================================================================

Based on the working TuyaDoubleSwitchDimmerGP quirk with these changes:
  1. Signature includes 0xED00 cluster (present in _TZE284 revision)
  2. DP 4/10 remapped from bulb_type → switch_type (writable via TuyaAttributesCluster)
  3. DP 5/11 added for max_brightness (maximum_level attribute added to level cluster)
  4. DP 14 added for power_on_behavior
  5. Brightness scale fixed: 0-254 (not 0-255) to match Z2M scale0_254to0_1000

KEY: TuyaAttributesCluster (not TuyaLocalCluster!) enables WRITING attributes
back to the device. TuyaLocalCluster only receives — it cannot send.
"""

from typing import Final

from zigpy.profiles import zgp, zha
import zigpy.types as t
from zigpy.zcl import foundation
from zigpy.zcl.clusters.general import Basic, GreenPowerProxy, Groups, Ota, Scenes, Time
from zigpy.zcl.foundation import ZCLAttributeDef

from zhaquirks.const import (
    DEVICE_TYPE,
    ENDPOINTS,
    INPUT_CLUSTERS,
    MODELS_INFO,
    OUTPUT_CLUSTERS,
    PROFILE_ID,
)
from zhaquirks.tuya import NoManufacturerCluster, TuyaDimmerSwitch
from zhaquirks.tuya.mcu import (
    DPToAttributeMapping,
    TuyaAttributesCluster,
    TuyaInWallLevelControl,
    TuyaLevelControl,
    TuyaLevelControlManufCluster,
    TuyaOnOff,
    TuyaOnOffNM,
)


# ---------------------------------------------------------------------------
# TuyaInWallLevelControlNM — handles ZCL move_to_level → Tuya DP
# This is the class that makes brightness control actually WORK.
# Without it, ZCL level commands have nothing to convert them to Tuya DPs.
#
# IMPORTANT: Base TuyaInWallLevelControl only defines minimum_level (0xEF01)
# and bulb_type (0xEF02). It does NOT have maximum_level.
# Without maximum_level, DP 5/11 reports from the device are silently lost
# and you cannot SET max brightness → the device keeps whatever was there.
# ---------------------------------------------------------------------------
class TuyaInWallLevelControlZDMS16(TuyaInWallLevelControl):
    """Extended level control with maximum_level attribute for ZDMS16."""

    class AttributeDefs(TuyaInWallLevelControl.AttributeDefs):
        """Add maximum_level attribute that base class is missing."""

        maximum_level: Final = ZCLAttributeDef(
            id=0xEF03, type=t.uint32_t, is_manufacturer_specific=True
        )


class TuyaInWallLevelControlNM(NoManufacturerCluster, TuyaInWallLevelControlZDMS16):
    """Tuya Level cluster for inwall dimmable device with NoManufacturerID."""


# ---------------------------------------------------------------------------
# Enum types — ZHA resolves values by member NAME, so we need named members.
# Without these, writing "2" fails with KeyError because t.enum8 has no
# member named "2".
# ---------------------------------------------------------------------------
class SwitchType(t.enum8):
    """Tuya switch type enum (switchType2 from Z2M)."""

    toggle = 0x00
    state = 0x01
    momentary = 0x02


class PowerOnBehavior(t.enum8):
    """Tuya power-on behavior enum (powerOnBehaviorEnum from Z2M)."""

    off = 0x00
    on = 0x01
    previous = 0x02


# ---------------------------------------------------------------------------
# Switch Type cluster — WRITABLE (TuyaAttributesCluster, not TuyaLocalCluster!)
#
# TuyaAttributesCluster.write_attributes() sends TUYA_MCU_COMMAND on the Bus
# → TuyaMCUCluster.tuya_mcu_command() catches it
# → get_dp_mapping() finds DP 4 or 10 based on ep_attribute + attribute_name
# → from_cluster_data() converts and sends Tuya DP to the device
#
# This is what makes writing switch_type (e.g. changing to momentary) work!
# ---------------------------------------------------------------------------
class TuyaSwitchTypeCluster(TuyaAttributesCluster):
    """Switch type configuration — WRITABLE.

    toggle    = 0 (standard toggle switch)
    state     = 1 (latching push button)
    momentary = 2 (momentary / impulse push button)
    """

    cluster_id = 0xFC00
    name = "Tuya Switch Type"
    ep_attribute = "tuya_switch_type"

    class AttributeDefs(foundation.BaseAttributeDefs):
        switch_type = foundation.ZCLAttributeDef(
            id=0x0000,
            type=SwitchType,
            is_manufacturer_specific=True,
        )


# ---------------------------------------------------------------------------
# Power On Behavior cluster — WRITABLE
# ---------------------------------------------------------------------------
class TuyaPowerOnBehaviorCluster(TuyaAttributesCluster):
    """Power-on behavior — WRITABLE.

    off      = 0 (turn off after power restore)
    on       = 1 (turn on after power restore)
    previous = 2 (restore last state)
    """

    cluster_id = 0xFC01
    name = "Tuya Power On Behavior"
    ep_attribute = "tuya_power_on_behavior"

    class AttributeDefs(foundation.BaseAttributeDefs):
        power_on_behavior = foundation.ZCLAttributeDef(
            id=0x0000,
            type=PowerOnBehavior,
            is_manufacturer_specific=True,
        )


# ---------------------------------------------------------------------------
# Override TuyaLevelControlManufCluster with correct DP mapping for ZDMS16
# DP 4/10 → switch_type (not bulb_type)
# DP 5/11 → max_brightness (not present in base class)
# DP 14   → power_on_behavior
# ---------------------------------------------------------------------------
class ZDMS16ManufCluster(TuyaLevelControlManufCluster):
    """Tuya MCU cluster for ZDMS16-2/Y2 with corrected DP mapping.

    Overrides parent's dp_to_attribute completely because:
    - DP 4/10 are switch_type, not bulb_type
    - DP 5/11 (max_brightness) are missing from parent
    - DP 14 (power_on_behavior) is missing from parent
    """

    dp_to_attribute: dict[int, DPToAttributeMapping] = {
        # === Channel 1 (endpoint 1) ===
        1: DPToAttributeMapping(
            TuyaOnOff.ep_attribute,
            "on_off",
        ),
        # Z2M uses scale0_254to0_1000: ZCL 0-254 ↔ Tuya 0-1000
        # Using 254 (not 255!) so that ZCL 254 (100%) maps to Tuya 1000 exactly
        2: DPToAttributeMapping(
            TuyaLevelControl.ep_attribute,
            "current_level",
            converter=lambda x: (x * 254) // 1000,
            dp_converter=lambda x: (x * 1000) // 254,
        ),
        3: DPToAttributeMapping(
            TuyaLevelControl.ep_attribute,
            "minimum_level",
            converter=lambda x: (x * 254) // 1000,
            dp_converter=lambda x: (x * 1000) // 254,
        ),
        4: DPToAttributeMapping(
            TuyaSwitchTypeCluster.ep_attribute,  # "tuya_switch_type"
            "switch_type",
        ),
        5: DPToAttributeMapping(
            TuyaLevelControl.ep_attribute,
            "maximum_level",
            converter=lambda x: (x * 254) // 1000,
            dp_converter=lambda x: (x * 1000) // 254,
        ),
        # === Channel 2 (endpoint 2) ===
        7: DPToAttributeMapping(
            TuyaOnOff.ep_attribute,
            "on_off",
            endpoint_id=2,
        ),
        8: DPToAttributeMapping(
            TuyaLevelControl.ep_attribute,
            "current_level",
            converter=lambda x: (x * 254) // 1000,
            dp_converter=lambda x: (x * 1000) // 254,
            endpoint_id=2,
        ),
        9: DPToAttributeMapping(
            TuyaLevelControl.ep_attribute,
            "minimum_level",
            converter=lambda x: (x * 254) // 1000,
            dp_converter=lambda x: (x * 1000) // 254,
            endpoint_id=2,
        ),
        10: DPToAttributeMapping(
            TuyaSwitchTypeCluster.ep_attribute,  # "tuya_switch_type"
            "switch_type",
            endpoint_id=2,
        ),
        11: DPToAttributeMapping(
            TuyaLevelControl.ep_attribute,
            "maximum_level",
            converter=lambda x: (x * 254) // 1000,
            dp_converter=lambda x: (x * 1000) // 254,
            endpoint_id=2,
        ),
        # === Global ===
        14: DPToAttributeMapping(
            TuyaPowerOnBehaviorCluster.ep_attribute,  # "tuya_power_on_behavior"
            "power_on_behavior",
        ),
    }

    data_point_handlers = {
        1: "_dp_2_attr_update",
        2: "_dp_2_attr_update",
        3: "_dp_2_attr_update",
        4: "_dp_2_attr_update",
        5: "_dp_2_attr_update",
        7: "_dp_2_attr_update",
        8: "_dp_2_attr_update",
        9: "_dp_2_attr_update",
        10: "_dp_2_attr_update",
        11: "_dp_2_attr_update",
        14: "_dp_2_attr_update",
    }


# ---------------------------------------------------------------------------
# Device
# ---------------------------------------------------------------------------
class AvattoZDMS16Y2(TuyaDimmerSwitch):
    """Avatto ZDMS16-Y2 Zigbee 2 Channel Dimmer (_TZE284_jtbgusdc).

    Signature verified against database entry from:
    https://github.com/Koenkk/zigbee2mqtt/issues/28341
    """

    signature = {
        MODELS_INFO: [
            ("_TZE284_jtbgusdc", "TS0601"),
        ],
        ENDPOINTS: {
            # profId:260, devId:81(0x0051), inClusterList:[4,5,61184,0,60672]
            # outClusterList:[25,10]
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,                         # 0 (0x0000)
                    Groups.cluster_id,                        # 4 (0x0004)
                    Scenes.cluster_id,                        # 5 (0x0005)
                    TuyaLevelControlManufCluster.cluster_id,  # 61184 (0xEF00)
                    0xED00,                                   # 60672
                ],
                OUTPUT_CLUSTERS: [
                    Ota.cluster_id,   # 25 (0x0019)
                    Time.cluster_id,  # 10 (0x000A)
                ],
            },
            # profId:41440, devId:97(0x0061), in:[], out:[33(0x0021)]
            242: {
                PROFILE_ID: zgp.PROFILE_ID,
                DEVICE_TYPE: zgp.DeviceType.PROXY_BASIC,
                INPUT_CLUSTERS: [],
                OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
            },
        },
    }

    replacement = {
        ENDPOINTS: {
            # Channel 1
            1: {
                DEVICE_TYPE: zha.DeviceType.ON_OFF_LIGHT,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    ZDMS16ManufCluster,          # 0xEF00 — MCU with corrected DP map
                    TuyaOnOffNM,                 # On/Off (DP 1) — handles on/off commands
                    TuyaInWallLevelControlNM,    # Level (DP 2) — handles move_to_level
                    TuyaSwitchTypeCluster,       # Switch type (DP 4) — 0xFC00, WRITABLE
                    TuyaPowerOnBehaviorCluster,  # Power-on (DP 14) — 0xFC01, WRITABLE
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            # Channel 2
            2: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.ON_OFF_LIGHT,
                INPUT_CLUSTERS: [
                    TuyaOnOffNM,                 # On/Off (DP 7)
                    TuyaInWallLevelControlNM,    # Level (DP 8)
                    TuyaSwitchTypeCluster,       # Switch type (DP 10) — 0xFC00, WRITABLE
                ],
                OUTPUT_CLUSTERS: [],
            },
            # Green Power
            242: {
                PROFILE_ID: zgp.PROFILE_ID,
                DEVICE_TYPE: zgp.DeviceType.PROXY_BASIC,
                INPUT_CLUSTERS: [],
                OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
            },
        },
    }
1 Like

Fantastic, you’re a hero may the light be with you!