Ensure Tuya fans have at least one valid DPCode (#150550)

This commit is contained in:
epenet
2025-08-13 13:40:11 +02:00
committed by GitHub
parent ff694a0058
commit 51413b7a8d
3 changed files with 34 additions and 171 deletions

View File

@@ -26,6 +26,16 @@ from .entity import TuyaEntity
from .models import EnumTypeData, IntegerTypeData from .models import EnumTypeData, IntegerTypeData
from .util import get_dpcode from .util import get_dpcode
_DIRECTION_DPCODES = (DPCode.FAN_DIRECTION,)
_OSCILLATE_DPCODES = (DPCode.SWITCH_HORIZONTAL, DPCode.SWITCH_VERTICAL)
_SPEED_DPCODES = (
DPCode.FAN_SPEED_PERCENT,
DPCode.FAN_SPEED,
DPCode.SPEED,
DPCode.FAN_SPEED_ENUM,
)
_SWITCH_DPCODES = (DPCode.SWITCH_FAN, DPCode.FAN_SWITCH, DPCode.SWITCH)
TUYA_SUPPORT_TYPE = { TUYA_SUPPORT_TYPE = {
# Dehumidifier # Dehumidifier
# https://developer.tuya.com/en/docs/iot/categorycs?id=Kaiuz1vcz4dha # https://developer.tuya.com/en/docs/iot/categorycs?id=Kaiuz1vcz4dha
@@ -47,6 +57,19 @@ TUYA_SUPPORT_TYPE = {
} }
def _has_a_valid_dpcode(device: CustomerDevice) -> bool:
"""Check if the device has at least one valid DP code."""
properties_to_check: list[DPCode | tuple[DPCode, ...] | None] = [
# Main control switch
_SWITCH_DPCODES,
# Other properties
_SPEED_DPCODES,
_OSCILLATE_DPCODES,
_DIRECTION_DPCODES,
]
return any(get_dpcode(device, code) for code in properties_to_check)
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
entry: TuyaConfigEntry, entry: TuyaConfigEntry,
@@ -61,7 +84,7 @@ async def async_setup_entry(
entities: list[TuyaFanEntity] = [] entities: list[TuyaFanEntity] = []
for device_id in device_ids: for device_id in device_ids:
device = hass_data.manager.device_map[device_id] device = hass_data.manager.device_map[device_id]
if device and device.category in TUYA_SUPPORT_TYPE: if device.category in TUYA_SUPPORT_TYPE and _has_a_valid_dpcode(device):
entities.append(TuyaFanEntity(device, hass_data.manager)) entities.append(TuyaFanEntity(device, hass_data.manager))
async_add_entities(entities) async_add_entities(entities)
@@ -91,9 +114,7 @@ class TuyaFanEntity(TuyaEntity, FanEntity):
"""Init Tuya Fan Device.""" """Init Tuya Fan Device."""
super().__init__(device, device_manager) super().__init__(device, device_manager)
self._switch = get_dpcode( self._switch = get_dpcode(self.device, _SWITCH_DPCODES)
self.device, (DPCode.SWITCH_FAN, DPCode.FAN_SWITCH, DPCode.SWITCH)
)
self._attr_preset_modes = [] self._attr_preset_modes = []
if enum_type := self.find_dpcode( if enum_type := self.find_dpcode(
@@ -104,31 +125,23 @@ class TuyaFanEntity(TuyaEntity, FanEntity):
self._attr_preset_modes = enum_type.range self._attr_preset_modes = enum_type.range
# Find speed controls, can be either percentage or a set of speeds # Find speed controls, can be either percentage or a set of speeds
dpcodes = (
DPCode.FAN_SPEED_PERCENT,
DPCode.FAN_SPEED,
DPCode.SPEED,
DPCode.FAN_SPEED_ENUM,
)
if int_type := self.find_dpcode( if int_type := self.find_dpcode(
dpcodes, dptype=DPType.INTEGER, prefer_function=True _SPEED_DPCODES, dptype=DPType.INTEGER, prefer_function=True
): ):
self._attr_supported_features |= FanEntityFeature.SET_SPEED self._attr_supported_features |= FanEntityFeature.SET_SPEED
self._speed = int_type self._speed = int_type
elif enum_type := self.find_dpcode( elif enum_type := self.find_dpcode(
dpcodes, dptype=DPType.ENUM, prefer_function=True _SPEED_DPCODES, dptype=DPType.ENUM, prefer_function=True
): ):
self._attr_supported_features |= FanEntityFeature.SET_SPEED self._attr_supported_features |= FanEntityFeature.SET_SPEED
self._speeds = enum_type self._speeds = enum_type
if dpcode := get_dpcode( if dpcode := get_dpcode(self.device, _OSCILLATE_DPCODES):
self.device, (DPCode.SWITCH_HORIZONTAL, DPCode.SWITCH_VERTICAL)
):
self._oscillate = dpcode self._oscillate = dpcode
self._attr_supported_features |= FanEntityFeature.OSCILLATE self._attr_supported_features |= FanEntityFeature.OSCILLATE
if enum_type := self.find_dpcode( if enum_type := self.find_dpcode(
DPCode.FAN_DIRECTION, dptype=DPType.ENUM, prefer_function=True _DIRECTION_DPCODES, dptype=DPType.ENUM, prefer_function=True
): ):
self._direction = enum_type self._direction = enum_type
self._attr_supported_features |= FanEntityFeature.DIRECTION self._attr_supported_features |= FanEntityFeature.DIRECTION

View File

@@ -204,9 +204,9 @@
'platform': 'tuya', 'platform': 'tuya',
'previous_unique_id': None, 'previous_unique_id': None,
'suggested_object_id': None, 'suggested_object_id': None,
'supported_features': 0, 'supported_features': <FanEntityFeature: 48>,
'translation_key': None, 'translation_key': None,
'unique_id': 'tuya.ilms5pwjzzsxuxmvsc', 'unique_id': 'tuya.2myxayqtud9aqbizsc',
'unit_of_measurement': None, 'unit_of_measurement': None,
}) })
# --- # ---
@@ -214,116 +214,16 @@
StateSnapshot({ StateSnapshot({
'attributes': ReadOnlyDict({ 'attributes': ReadOnlyDict({
'friendly_name': 'Dehumidifier', 'friendly_name': 'Dehumidifier',
'supported_features': <FanEntityFeature: 0>, 'supported_features': <FanEntityFeature: 48>,
}), }),
'context': <ANY>, 'context': <ANY>,
'entity_id': 'fan.dehumidifier', 'entity_id': 'fan.dehumidifier',
'last_changed': <ANY>, 'last_changed': <ANY>,
'last_reported': <ANY>, 'last_reported': <ANY>,
'last_updated': <ANY>, 'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_platform_setup_and_discovery[fan.dehumidifier_2-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'fan',
'entity_category': None,
'entity_id': 'fan.dehumidifier_2',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': None,
'platform': 'tuya',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <FanEntityFeature: 48>,
'translation_key': None,
'unique_id': 'tuya.2myxayqtud9aqbizsc',
'unit_of_measurement': None,
})
# ---
# name: test_platform_setup_and_discovery[fan.dehumidifier_2-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Dehumidifier',
'supported_features': <FanEntityFeature: 48>,
}),
'context': <ANY>,
'entity_id': 'fan.dehumidifier_2',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on', 'state': 'on',
}) })
# --- # ---
# name: test_platform_setup_and_discovery[fan.dryfix-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'fan',
'entity_category': None,
'entity_id': 'fan.dryfix',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': None,
'platform': 'tuya',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': 'tuya.hz4pau766eavmxhqsc',
'unit_of_measurement': None,
})
# ---
# name: test_platform_setup_and_discovery[fan.dryfix-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'DryFix',
'supported_features': <FanEntityFeature: 0>,
}),
'context': <ANY>,
'entity_id': 'fan.dryfix',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unavailable',
})
# ---
# name: test_platform_setup_and_discovery[fan.hl400-entry] # name: test_platform_setup_and_discovery[fan.hl400-entry]
EntityRegistryEntrySnapshot({ EntityRegistryEntrySnapshot({
'aliases': set({ 'aliases': set({
@@ -556,53 +456,3 @@
'state': 'off', 'state': 'off',
}) })
# --- # ---
# name: test_platform_setup_and_discovery[fan.ventilador_cama-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'fan',
'entity_category': None,
'entity_id': 'fan.ventilador_cama',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': None,
'platform': 'tuya',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': 'tuya.c1tfgunpf6optybisf',
'unit_of_measurement': None,
})
# ---
# name: test_platform_setup_and_discovery[fan.ventilador_cama-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Ventilador Cama',
'supported_features': <FanEntityFeature: 0>,
}),
'context': <ANY>,
'entity_id': 'fan.ventilador_cama',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---

View File

@@ -1601,7 +1601,7 @@
'labels': set({ 'labels': set({
}), }),
'manufacturer': 'Tuya', 'manufacturer': 'Tuya',
'model': 'Tower bladeless fan ', 'model': 'Tower bladeless fan (unsupported)',
'model_id': 'ibytpo6fpnugft1c', 'model_id': 'ibytpo6fpnugft1c',
'name': 'Ventilador Cama', 'name': 'Ventilador Cama',
'name_by_user': None, 'name_by_user': None,
@@ -2593,7 +2593,7 @@
'labels': set({ 'labels': set({
}), }),
'manufacturer': 'Tuya', 'manufacturer': 'Tuya',
'model': '', 'model': ' (unsupported)',
'model_id': 'qhxmvae667uap4zh', 'model_id': 'qhxmvae667uap4zh',
'name': 'DryFix', 'name': 'DryFix',
'name_by_user': None, 'name_by_user': None,
@@ -2779,7 +2779,7 @@
'labels': set({ 'labels': set({
}), }),
'manufacturer': 'Tuya', 'manufacturer': 'Tuya',
'model': 'the Smart Dry Plus™ Connect Dehumidifier ', 'model': 'the Smart Dry Plus™ Connect Dehumidifier (unsupported)',
'model_id': 'vmxuxszzjwp5smli', 'model_id': 'vmxuxszzjwp5smli',
'name': 'Dehumidifier ', 'name': 'Dehumidifier ',
'name_by_user': None, 'name_by_user': None,