-
Notifications
You must be signed in to change notification settings - Fork 960
Open
Labels
device support requestThis requests support for a new deviceThis requests support for a new device
Description
Problem description
Tuya TS0601-based LED strip controller, produced by Gledopto.
Solution description
Support for this device exists in Zigbee2MQTT, so I used this as a start to write a quirk.
However, I cannot get the color part to work, because I need to send a raw payload to DP 61. On/off and level control seem to work.
Quirk V2 lacks some examples on RGB/Lights, so I have no clue how to continue.
Screenshots/Video
None
Diagnostics information
None
Device signature
Device signature
{
"node_descriptor": {
"logical_type": 1,
"complex_descriptor_available": 0,
"user_descriptor_available": 0,
"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": "0x0104",
"device_type": "0x0051",
"input_clusters": [
"0x0000",
"0x0004",
"0x0005",
"0xed00",
"0xef00"
],
"output_clusters": [
"0x000a",
"0x0019"
]
},
"242": {
"profile_id": "0xa1e0",
"device_type": "0x0061",
"input_clusters": [],
"output_clusters": [
"0x0021"
]
}
},
"manufacturer": "_TZE284_gt5al3bl",
"model": "TS0601",
"class": "zigpy.device.Device"
}Logs
None
Custom quirk
Custom quirk
"""Tuya SPI Pixel Controller GL-SPI-206P quirk."""
from zigpy.quirks.v2 import EntityType
import zigpy.types as t
from zigpy.zcl.clusters.general import LevelControl
from zigpy.zcl.clusters.lighting import Color
from zhaquirks.tuya import TuyaLocalCluster, NoManufacturerCluster, TuyaData
from zhaquirks.tuya.mcu import (
TuyaLevelControl,
TuyaDatapointData,
TUYA_MCU_COMMAND,
)
from zhaquirks.tuya.builder import TuyaQuirkBuilder
from zigpy.profiles import zha
from zhaquirks.tuya.mcu import DPToAttributeMapping
from zigpy.zcl import foundation
from homeassistant.util.color import color_xy_to_hs
from typing import Any
class WorkMode(t.enum8):
"""Work mode enum."""
White = 0x00
Colour = 0x01
Scene = 0x02
Music = 0x03
class ChipType(t.enum8):
"""Chip type enum."""
WS2801 = 0x00
LPD6803 = 0x01
LPD8803 = 0x02
WS2811 = 0x03
TM1814B = 0x04
TM1934A = 0x05
SK6812 = 0x06
SK9822 = 0x07
UCS8904B = 0x08
WS2805 = 0x09
class TuyaColor(Color, TuyaLocalCluster):
"""Tuya MCU Level cluster for color device."""
_CONSTANT_ATTRIBUTES = {
Color.AttributeDefs.color_mode.id: Color.ColorMode.Hue_and_saturation
}
class AttributeDefs(Color.AttributeDefs):
"""Cluster attributes."""
def _xy_to_hsv(self, x: int, y: int) -> tuple[int, int, int]:
"""Convert CIE xy to HSV for Tuya."""
# Normalize x, y (ZCL sends as uint16 where 65535 = 1.0)
x_norm = x / 65535
y_norm = y / 65535
# Use Home Assistant's color utility to convert xy to HS
hue_deg, sat_percent = color_xy_to_hs(x_norm, y_norm)
# Convert to Tuya format (0-360 for hue, 0-1000 for saturation)
hue = max(0, min(360, round(hue_deg)))
sat = max(0, min(1000, round(sat_percent * 10)))
val = 1000 # Fixed at 1000 as per TypeScript implementation
return (hue, sat, val)
def _send_dp61_color(self, hue: int, sat: int, val: int = 1000):
"""Send DP 61 raw payload with HSV data."""
# Build the 11-byte payload as per TypeScript implementation
payload = bytes([
0x00, 0x01, 0x01, 0x14, 0x00, # Header
(hue >> 8) & 0xFF, hue & 0xFF, # Hue (16-bit big-endian)
(sat >> 8) & 0xFF, sat & 0xFF, # Saturation (16-bit)
(val >> 8) & 0xFF, val & 0xFF, # Value (16-bit)
])
self.debug(
"Sending DP 61 color: H=%d S=%d V=%d, payload=%s",
hue, sat, val, payload.hex()
)
# Send via Tuya MCU command bus with the raw payload as attribute value
cluster_data = TuyaDatapointData(
dp=61,
data=TuyaData(raw=payload)
)
self.endpoint.device.command_bus.listener_event(
TUYA_MCU_COMMAND,
cluster_data,
)
async def command(
self,
command_id: foundation.GeneralCommand | int | t.uint8_t,
*args,
manufacturer: int | t.uint16_t | None = None,
expect_reply: bool = True,
tsn: int | t.uint8_t | None = None,
**kwargs: Any,
):
"""Override the default Cluster command."""
self.debug(
"Tuya Cluster Command: %x, args=%s, kwargs=%s",
command_id,
args,
kwargs,
)
if command_id == Color.ServerCommandDefs.move_to_color.id:
x = kwargs.get("color_x")
y = kwargs.get("color_y")
hue, sat, val = self._xy_to_hsv(x, y)
self._send_dp61_color(hue, sat, val)
return foundation.GENERAL_COMMANDS[
foundation.GeneralCommand.Default_Response
].schema(command_id=command_id, status=foundation.Status.SUCCESS)
class TuyaColorNM(NoManufacturerCluster, TuyaColor):
"""Tuya Color cluster for SPI Pixel Controller."""
class TuyaLevelControlNM(NoManufacturerCluster, TuyaLevelControl):
"""Tuya OnOff cluster with NoManufacturerID."""
(
TuyaQuirkBuilder("_TZE284_gt5al3bl", "TS0601")
# DP 1: On/Off (works)
.tuya_onoff(dp_id=1)
# DP 2: Work mode (not tested)
.tuya_enum(
dp_id=2,
attribute_name="work_mode",
enum_class=WorkMode,
entity_type=EntityType.CONFIG,
translation_key="work_mode",
fallback_name="Work mode",
)
# DP 3: Brightness/Level control (works)
.tuya_dp(
dp_id=3,
ep_attribute=TuyaLevelControlNM.ep_attribute,
attribute_name=LevelControl.AttributeDefs.current_level.name
)
.adds(TuyaLevelControlNM)
# DP 61: Color control (does not work, needs raw payload support)
.tuya_dp_multi(
dp_id=61,
attribute_mapping=[
DPToAttributeMapping(
ep_attribute=TuyaColorNM.ep_attribute,
attribute_name=Color.AttributeDefs.current_hue.name
),
])
.adds(TuyaColorNM)
# DP 53: Light pixel number setting (works)
.tuya_number(
dp_id=53,
attribute_name="lightpixel_number_set",
type=t.uint16_t,
min_value=1,
max_value=1000,
step=1,
translation_key="lightpixel_number_set",
fallback_name="Pixel count",
)
# DP 102: Chip type (works)
.tuya_enum(
dp_id=102,
attribute_name="chip_type",
enum_class=ChipType,
entity_type=EntityType.CONFIG,
translation_key="chip_type",
fallback_name="Chip type",
)
.replaces_endpoint(1, device_type=zha.DeviceType.COLOR_DIMMABLE_LIGHT)
.skip_configuration()
.add_to_registry()
)Additional information
None
Metadata
Metadata
Assignees
Labels
device support requestThis requests support for a new deviceThis requests support for a new device