diff --git a/tests/test_tuya.py b/tests/test_tuya.py index 35405ccff4..462cbe8941 100644 --- a/tests/test_tuya.py +++ b/tests/test_tuya.py @@ -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 @@ -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" @@ -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" ) @@ -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.""" diff --git a/zhaquirks/tuya/ts0021.py b/zhaquirks/tuya/ts0021.py new file mode 100644 index 0000000000..f6705b4666 --- /dev/null +++ b/zhaquirks/tuya/ts0021.py @@ -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}, + }, + }