diff --git a/tests/test_tuya_clusters.py b/tests/test_tuya_clusters.py index 9d2ac364ee..b675aec89d 100644 --- a/tests/test_tuya_clusters.py +++ b/tests/test_tuya_clusters.py @@ -4,6 +4,7 @@ import pytest import zigpy.types as t +from zigpy.zcl.clusters.general import OnOff import zigpy.zcl.foundation as zcl_f from zhaquirks.tuya import ( @@ -15,6 +16,8 @@ TuyaData, TuyaDatapointData, TuyaNewManufCluster, + TuyaSwitchModeSelectEntity, + TuyaZBOnOffAttributeCluster, ) @@ -291,3 +294,46 @@ def test_tuya_cluster_request_no_handler(default_rsp_mock, TuyaCluster): assert default_rsp_mock.call_count == 1 assert default_rsp_mock.call_args[1]["status"] == zcl_f.Status.UNSUP_CLUSTER_COMMAND + + +def _switch_mode_select_entity(zigpy_device_mock, cluster_cls, cluster_id): + """Build a TuyaSwitchModeSelectEntity backed by the given cluster class.""" + device = zigpy_device_mock() + endpoint = device.add_endpoint(1) + cluster = endpoint.add_input_cluster(cluster_id, cluster_cls(endpoint)) + + fake_endpoint = mock.MagicMock() + fake_endpoint.id = endpoint.endpoint_id + fake_device = mock.MagicMock() + fake_device.ieee = device.ieee + + return TuyaSwitchModeSelectEntity(fake_endpoint, fake_device, cluster=cluster) + + +def test_tuya_switch_mode_select_entity_wrong_cluster(zigpy_device_mock): + """Entity is not supported when the backing cluster isn't the Tuya one.""" + entity = _switch_mode_select_entity(zigpy_device_mock, OnOff, OnOff.cluster_id) + assert entity._is_supported() is False + + +def test_tuya_switch_mode_select_entity_no_cached_value(zigpy_device_mock): + """Entity is not supported until the switch_mode attribute has a cached value.""" + entity = _switch_mode_select_entity( + zigpy_device_mock, + TuyaZBOnOffAttributeCluster, + TuyaZBOnOffAttributeCluster.cluster_id, + ) + assert entity._is_supported() is False + + +def test_tuya_switch_mode_select_entity_supported(zigpy_device_mock): + """Entity is supported once the switch_mode attribute is cached.""" + entity = _switch_mode_select_entity( + zigpy_device_mock, + TuyaZBOnOffAttributeCluster, + TuyaZBOnOffAttributeCluster.cluster_id, + ) + entity.cluster.update_attribute( + TuyaZBOnOffAttributeCluster.AttributeDefs.switch_mode.name, 0 + ) + assert entity._is_supported() is True diff --git a/zhaquirks/tuya/__init__.py b/zhaquirks/tuya/__init__.py index c5ed8972fd..034d5006b2 100644 --- a/zhaquirks/tuya/__init__.py +++ b/zhaquirks/tuya/__init__.py @@ -9,6 +9,13 @@ import logging from typing import Any, Final +from zha.application.platforms import ( + AttrConfig, + ClusterConfig, + ClusterMatch, + register_entity, + select, +) import zigpy.types as t from zigpy.typing import UNDEFINED, AddressingMode, UndefinedType from zigpy.zcl import BaseAttributeDefs, foundation @@ -1760,3 +1767,30 @@ def _dp_2_attr_update(self, datapoint: TuyaDatapointData) -> None: | value.value ) cluster.update_attribute(mapped_attr.attribute_name, value) + + +@register_entity(TuyaZBOnOffAttributeCluster.cluster_id) +class TuyaSwitchModeSelectEntity(select.ZCLEnumSelectEntity): + """Representation of a ZHA backlight mode select entity.""" + + _unique_id_suffix = "switch_mode" + _attribute_name = "switch_mode" + _enum = SwitchMode + _attr_translation_key: str = "switch_mode" + + _cluster_match = ClusterMatch( + server_clusters=frozenset({TuyaZBOnOffAttributeCluster.cluster_id}), + ) + + _server_cluster_config = { + TuyaZBOnOffAttributeCluster.cluster_id: ClusterConfig( + attributes={ + "switch_mode": AttrConfig(read_on_startup=False), + }, + ), + } + + def _is_supported(self) -> bool: + if not isinstance(self.cluster, TuyaZBOnOffAttributeCluster): + return False + return super()._is_supported() diff --git a/zhaquirks/tuya/ts004f.py b/zhaquirks/tuya/ts004f.py index 1e679f1114..5f8b77ed2f 100644 --- a/zhaquirks/tuya/ts004f.py +++ b/zhaquirks/tuya/ts004f.py @@ -62,6 +62,7 @@ TuyaNoBindPowerConfigurationCluster, TuyaSmartRemoteOnOffCluster, TuyaZBExternalSwitchTypeCluster, + TuyaZBOnOffAttributeCluster, ) @@ -116,6 +117,7 @@ class TuyaSmartRemote004FROK(EnchantedDevice): TuyaNoBindPowerConfigurationCluster, Identify.cluster_id, Groups.cluster_id, # Is needed for adding group then binding is not working. + TuyaZBOnOffAttributeCluster, LightLink.cluster_id, ], OUTPUT_CLUSTERS: [ @@ -367,6 +369,7 @@ class TuyaSmartRemote004FSK(EnchantedDevice): TuyaNoBindPowerConfigurationCluster, Identify.cluster_id, Groups.cluster_id, # Is needed for adding group then binding is not working. + TuyaZBOnOffAttributeCluster, LightLink.cluster_id, TuyaZBExternalSwitchTypeCluster, ],