Skip to content

Commit

Permalink
Add support for the Tuya TS0021 2-gang switch (#3247)
Browse files Browse the repository at this point in the history
  • Loading branch information
rbaron authored Aug 11, 2024
1 parent ce63e58 commit 156aaa6
Show file tree
Hide file tree
Showing 2 changed files with 208 additions and 0 deletions.
69 changes: 69 additions & 0 deletions tests/test_tuya.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
)
from zhaquirks.tuya import Data, TuyaManufClusterAttributes, TuyaNewManufCluster
import zhaquirks.tuya.sm0202_motion
import zhaquirks.tuya.ts0021
import zhaquirks.tuya.ts0041
import zhaquirks.tuya.ts0042
import zhaquirks.tuya.ts0043
Expand All @@ -41,6 +42,12 @@

ZCL_TUYA_SET_TIME_REQUEST = b"\tp\x24\x00\00"

ZCL_TUYA_BUTTON_1_SINGLE_PRESS = b"\tT\x06\x01$\x01\x04\x00\x01\x00"
ZCL_TUYA_BUTTON_1_DOUBLE_PRESS = b"\tU\x06\x01%\x01\x04\x00\x01\x01"
ZCL_TUYA_BUTTON_1_LONG_PRESS = b"\tk\x06\x03\x11\x01\x04\x00\x01\x02"
ZCL_TUYA_BUTTON_2_SINGLE_PRESS = b"\tN\x06\x01\x1f\x02\x04\x00\x01\x00"
ZCL_TUYA_BUTTON_2_DOUBLE_PRESS = b"\tj\x06\x03\x10\x02\x04\x00\x01\x01"
ZCL_TUYA_BUTTON_2_LONG_PRESS = b"\tl\x06\x03\x12\x02\x04\x00\x01\x02"
ZCL_TUYA_MOTION = b"\tL\x01\x00\x05\x03\x04\x00\x01\x02"
ZCL_TUYA_SWITCH_ON = b"\tQ\x02\x006\x01\x01\x00\x01\x01"
ZCL_TUYA_SWITCH_OFF = b"\tQ\x02\x006\x01\x01\x00\x01\x00"
Expand All @@ -66,6 +73,7 @@
ZCL_TUYA_VALVE_AUTO_LOCK_ON = b"\t2\x01\x03\x04\x74\x01\x00\x01\x01"
ZCL_TUYA_VALVE_BATTERY_LOW = b"\t2\x01\x03\x04\x6e\x01\x00\x01\x01"


ZCL_TUYA_VALVE_ZONNSMART_TEMPERATURE = (
b"\tp\x01\x00\x02\x18\x02\x00\x04\x00\x00\x00\xd3"
)
Expand Down Expand Up @@ -137,6 +145,67 @@ async def test_singleswitch_state_report(zigpy_device_from_quirk, quirk):
assert switch_listener.attribute_updates[1][1] == OFF


@pytest.mark.parametrize(
"quirk,raw_event,expected_attr_name,expected_attr_value",
(
(
zhaquirks.tuya.ts0021.TS0021,
ZCL_TUYA_BUTTON_1_SINGLE_PRESS,
"btn_1_pressed",
0x00,
),
(
zhaquirks.tuya.ts0021.TS0021,
ZCL_TUYA_BUTTON_1_DOUBLE_PRESS,
"btn_1_pressed",
0x01,
),
(
zhaquirks.tuya.ts0021.TS0021,
ZCL_TUYA_BUTTON_1_LONG_PRESS,
"btn_1_pressed",
0x02,
),
(
zhaquirks.tuya.ts0021.TS0021,
ZCL_TUYA_BUTTON_2_SINGLE_PRESS,
"btn_2_pressed",
0x00,
),
(
zhaquirks.tuya.ts0021.TS0021,
ZCL_TUYA_BUTTON_2_DOUBLE_PRESS,
"btn_2_pressed",
0x01,
),
(
zhaquirks.tuya.ts0021.TS0021,
ZCL_TUYA_BUTTON_2_LONG_PRESS,
"btn_2_pressed",
0x02,
),
),
)
async def test_ts0021_switch(
zigpy_device_from_quirk, quirk, raw_event, expected_attr_name, expected_attr_value
):
"""Test tuya TS0021 2-gang switch."""

device = zigpy_device_from_quirk(quirk)

tuya_cluster = device.endpoints[1].tuya_manufacturer
switch_listener = ClusterListener(tuya_cluster)

hdr, args = tuya_cluster.deserialize(raw_event)
tuya_cluster.handle_message(hdr, args)

assert len(switch_listener.cluster_commands) == 1
assert len(switch_listener.attribute_updates) == 1

assert switch_listener.attribute_updates[0][0] == expected_attr_name
assert switch_listener.attribute_updates[0][1] == expected_attr_value


@pytest.mark.parametrize("quirk", (zhaquirks.tuya.ts0601_switch.TuyaDoubleSwitchTO,))
async def test_doubleswitch_state_report(zigpy_device_from_quirk, quirk):
"""Test tuya double switch."""
Expand Down
139 changes: 139 additions & 0 deletions zhaquirks/tuya/ts0021.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
"""Device handler the TS0021."""

from zigpy.profiles import zha
from zigpy.quirks import CustomDevice
from zigpy.zcl.clusters.general import Basic, Ota, PowerConfiguration, Time
from zigpy.zcl.clusters.security import IasZone

from zhaquirks import EventableCluster
from zhaquirks.const import (
ARGS,
ATTRIBUTE_ID,
CLUSTER_ID,
COMMAND,
COMMAND_ATTRIBUTE_UPDATED,
DEVICE_TYPE,
DOUBLE_PRESS,
ENDPOINT_ID,
ENDPOINTS,
INPUT_CLUSTERS,
LONG_PRESS,
MODELS_INFO,
OUTPUT_CLUSTERS,
PROFILE_ID,
SHORT_PRESS,
VALUE,
)
from zhaquirks.tuya import TUYA_CLUSTER_ID, DPToAttributeMapping, TuyaNewManufCluster

BTN_1 = "Button 1"
BTN_2 = "Button 2"

ATTR_BTN_1_PRESSED = "btn_1_pressed"
ATTR_BTN_2_PRESSED = "btn_2_pressed"


class TuyaCustomCluster(TuyaNewManufCluster, EventableCluster):
"""Tuya Custom Cluster for mapping data points to attributes."""

dp_to_attribute: dict[int, DPToAttributeMapping] = {
1: DPToAttributeMapping(
TuyaNewManufCluster.ep_attribute,
ATTR_BTN_1_PRESSED,
),
2: DPToAttributeMapping(
TuyaNewManufCluster.ep_attribute,
ATTR_BTN_2_PRESSED,
),
}

data_point_handlers = {
1: "_dp_2_attr_update",
2: "_dp_2_attr_update",
}


class TS0021(CustomDevice):
"""Tuya TS0021 2-button switch device."""

signature = {
# SizePrefixedSimpleDescriptor(endpoint=1, profile=260, device_type=1026,
# device_version=1,
# input_clusters=[1, 1280, 61184, 0],
# output_clusters=[25, 10])
MODELS_INFO: [("_TZ3210_3ulg9kpo", "TS0021")],
ENDPOINTS: {
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.IAS_ZONE,
INPUT_CLUSTERS: [
Basic.cluster_id,
PowerConfiguration.cluster_id,
IasZone.cluster_id,
TuyaNewManufCluster.cluster_id,
],
OUTPUT_CLUSTERS: [
Time.cluster_id,
Ota.cluster_id,
],
},
},
}

replacement = {
ENDPOINTS: {
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.IAS_ZONE,
INPUT_CLUSTERS: [
Basic.cluster_id,
PowerConfiguration.cluster_id,
IasZone.cluster_id,
TuyaCustomCluster,
],
OUTPUT_CLUSTERS: [
Time.cluster_id,
Ota.cluster_id,
],
},
},
}

device_automation_triggers = {
(SHORT_PRESS, BTN_1): {
COMMAND: COMMAND_ATTRIBUTE_UPDATED,
CLUSTER_ID: TUYA_CLUSTER_ID,
ENDPOINT_ID: 1,
ARGS: {ATTRIBUTE_ID: ATTR_BTN_1_PRESSED, VALUE: 0},
},
(DOUBLE_PRESS, BTN_1): {
COMMAND: COMMAND_ATTRIBUTE_UPDATED,
CLUSTER_ID: TUYA_CLUSTER_ID,
ENDPOINT_ID: 1,
ARGS: {ATTRIBUTE_ID: ATTR_BTN_1_PRESSED, VALUE: 1},
},
(LONG_PRESS, BTN_1): {
COMMAND: COMMAND_ATTRIBUTE_UPDATED,
CLUSTER_ID: TUYA_CLUSTER_ID,
ENDPOINT_ID: 1,
ARGS: {ATTRIBUTE_ID: ATTR_BTN_1_PRESSED, VALUE: 2},
},
(SHORT_PRESS, BTN_2): {
COMMAND: COMMAND_ATTRIBUTE_UPDATED,
CLUSTER_ID: TUYA_CLUSTER_ID,
ENDPOINT_ID: 1,
ARGS: {ATTRIBUTE_ID: ATTR_BTN_2_PRESSED, VALUE: 0},
},
(DOUBLE_PRESS, BTN_2): {
COMMAND: COMMAND_ATTRIBUTE_UPDATED,
CLUSTER_ID: TUYA_CLUSTER_ID,
ENDPOINT_ID: 1,
ARGS: {ATTRIBUTE_ID: ATTR_BTN_2_PRESSED, VALUE: 1},
},
(LONG_PRESS, BTN_2): {
COMMAND: COMMAND_ATTRIBUTE_UPDATED,
CLUSTER_ID: TUYA_CLUSTER_ID,
ENDPOINT_ID: 1,
ARGS: {ATTRIBUTE_ID: ATTR_BTN_2_PRESSED, VALUE: 2},
},
}

0 comments on commit 156aaa6

Please sign in to comment.