1
0
mirror of https://github.com/home-assistant/core.git synced 2025-12-24 12:59:34 +00:00

Migrate Tuya fan (speed) to use wrapper class (#156976)

This commit is contained in:
epenet
2025-11-21 11:45:42 +01:00
committed by GitHub
parent 9a22808499
commit bfdff46859
2 changed files with 61 additions and 72 deletions

View File

@@ -26,8 +26,8 @@ from .entity import TuyaEntity
from .models import (
DPCodeBooleanWrapper,
DPCodeEnumWrapper,
DPCodeIntegerWrapper,
EnumTypeData,
IntegerTypeData,
find_dpcode,
)
from .util import get_dpcode
@@ -79,6 +79,55 @@ def _has_a_valid_dpcode(device: CustomerDevice) -> bool:
return any(get_dpcode(device, code) for code in properties_to_check)
class _FanSpeedEnumWrapper(DPCodeEnumWrapper):
"""Wrapper for fan speed DP code (from an enum)."""
def get_speed_count(self) -> int:
"""Get the number of speeds supported by the fan."""
return len(self.type_information.range)
def read_device_status(self, device: CustomerDevice) -> int | None: # type: ignore[override]
"""Get the current speed as a percentage."""
if (value := super().read_device_status(device)) is None:
return None
return ordered_list_item_to_percentage(self.type_information.range, value)
def _convert_value_to_raw_value(self, device: CustomerDevice, value: Any) -> Any:
"""Convert a Home Assistant value back to a raw device value."""
return percentage_to_ordered_list_item(self.type_information.range, value)
class _FanSpeedIntegerWrapper(DPCodeIntegerWrapper):
"""Wrapper for fan speed DP code (from an integer)."""
def get_speed_count(self) -> int:
"""Get the number of speeds supported by the fan."""
return 100
def read_device_status(self, device: CustomerDevice) -> int | None:
"""Get the current speed as a percentage."""
if (value := super().read_device_status(device)) is None:
return None
return round(self.type_information.remap_value_to(value, 1, 100))
def _convert_value_to_raw_value(self, device: CustomerDevice, value: Any) -> Any:
"""Convert a Home Assistant value back to a raw device value."""
return round(self.type_information.remap_value_from(value, 1, 100))
def _get_speed_wrapper(
device: CustomerDevice,
) -> _FanSpeedEnumWrapper | _FanSpeedIntegerWrapper | None:
"""Get the speed wrapper for the device."""
if int_wrapper := _FanSpeedIntegerWrapper.find_dpcode(
device, _SPEED_DPCODES, prefer_function=True
):
return int_wrapper
return _FanSpeedEnumWrapper.find_dpcode(
device, _SPEED_DPCODES, prefer_function=True
)
async def async_setup_entry(
hass: HomeAssistant,
entry: TuyaConfigEntry,
@@ -104,6 +153,7 @@ async def async_setup_entry(
mode_wrapper=DPCodeEnumWrapper.find_dpcode(
device, _MODE_DPCODES, prefer_function=True
),
speed_wrapper=_get_speed_wrapper(device),
switch_wrapper=DPCodeBooleanWrapper.find_dpcode(
device, _SWITCH_DPCODES, prefer_function=True
),
@@ -122,8 +172,6 @@ class TuyaFanEntity(TuyaEntity, FanEntity):
"""Tuya Fan Device."""
_oscillate: DPCode | None = None
_speed: IntegerTypeData | None = None
_speeds: EnumTypeData | None = None
_attr_name = None
def __init__(
@@ -133,29 +181,23 @@ class TuyaFanEntity(TuyaEntity, FanEntity):
*,
direction_wrapper: _DirectionEnumWrapper | None,
mode_wrapper: DPCodeEnumWrapper | None,
speed_wrapper: _FanSpeedEnumWrapper | _FanSpeedIntegerWrapper | None,
switch_wrapper: DPCodeBooleanWrapper | None,
) -> None:
"""Init Tuya Fan Device."""
super().__init__(device, device_manager)
self._direction_wrapper = direction_wrapper
self._mode_wrapper = mode_wrapper
self._speed_wrapper = speed_wrapper
self._switch_wrapper = switch_wrapper
if mode_wrapper:
self._attr_supported_features |= FanEntityFeature.PRESET_MODE
self._attr_preset_modes = mode_wrapper.type_information.range
# Find speed controls, can be either percentage or a set of speeds
if int_type := find_dpcode(
self.device, _SPEED_DPCODES, dptype=DPType.INTEGER, prefer_function=True
):
if speed_wrapper:
self._attr_supported_features |= FanEntityFeature.SET_SPEED
self._speed = int_type
elif enum_type := find_dpcode(
self.device, _SPEED_DPCODES, dptype=DPType.ENUM, prefer_function=True
):
self._attr_supported_features |= FanEntityFeature.SET_SPEED
self._speeds = enum_type
self._attr_speed_count = speed_wrapper.get_speed_count()
if dpcode := get_dpcode(self.device, _OSCILLATE_DPCODES):
self._oscillate = dpcode
@@ -176,30 +218,9 @@ class TuyaFanEntity(TuyaEntity, FanEntity):
"""Set the direction of the fan."""
await self._async_send_dpcode_update(self._direction_wrapper, direction)
def set_percentage(self, percentage: int) -> None:
async def async_set_percentage(self, percentage: int) -> None:
"""Set the speed of the fan, as a percentage."""
if self._speed is not None:
self._send_command(
[
{
"code": self._speed.dpcode,
"value": int(self._speed.remap_value_from(percentage, 1, 100)),
}
]
)
return
if self._speeds is not None:
self._send_command(
[
{
"code": self._speeds.dpcode,
"value": percentage_to_ordered_list_item(
self._speeds.range, percentage
),
}
]
)
await self._async_send_dpcode_update(self._speed_wrapper, percentage)
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the fan off."""
@@ -219,22 +240,9 @@ class TuyaFanEntity(TuyaEntity, FanEntity):
self._switch_wrapper.get_update_command(self.device, True)
]
if percentage is not None and self._speed is not None:
if percentage is not None and self._speed_wrapper is not None:
commands.append(
{
"code": self._speed.dpcode,
"value": int(self._speed.remap_value_from(percentage, 1, 100)),
}
)
if percentage is not None and self._speeds is not None:
commands.append(
{
"code": self._speeds.dpcode,
"value": percentage_to_ordered_list_item(
self._speeds.range, percentage
),
}
self._speed_wrapper.get_update_command(self.device, percentage)
)
if preset_mode is not None and self._mode_wrapper:
@@ -274,23 +282,4 @@ class TuyaFanEntity(TuyaEntity, FanEntity):
@property
def percentage(self) -> int | None:
"""Return the current speed."""
if self._speed is not None:
if (value := self.device.status.get(self._speed.dpcode)) is None:
return None
return int(self._speed.remap_value_to(value, 1, 100))
if self._speeds is not None:
if (
value := self.device.status.get(self._speeds.dpcode)
) is None or value not in self._speeds.range:
return None
return ordered_list_item_to_percentage(self._speeds.range, value)
return None
@property
def speed_count(self) -> int:
"""Return the number of speeds the fan supports."""
if self._speeds is not None:
return len(self._speeds.range)
return 100
return self._read_wrapper(self._speed_wrapper)

View File

@@ -148,7 +148,7 @@
'attributes': ReadOnlyDict({
'direction': 'forward',
'friendly_name': 'ceiling fan/Light v2',
'percentage': 20,
'percentage': 21,
'percentage_step': 1.0,
'preset_mode': None,
'preset_modes': None,