Remove legacy fan compatibility shim (#59781)
parent
566716d697
commit
7fbe1dbc99
|
@ -2,10 +2,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from homeassistant.components.fan import (
|
||||
SPEED_HIGH,
|
||||
SPEED_LOW,
|
||||
SPEED_MEDIUM,
|
||||
SPEED_OFF,
|
||||
SUPPORT_DIRECTION,
|
||||
SUPPORT_OSCILLATE,
|
||||
SUPPORT_PRESET_MODE,
|
||||
|
@ -26,33 +22,25 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
|||
"""Set up the demo fan platform."""
|
||||
async_add_entities(
|
||||
[
|
||||
# These fans implement the old model
|
||||
DemoFan(
|
||||
DemoPercentageFan(
|
||||
hass,
|
||||
"fan1",
|
||||
"Living Room Fan",
|
||||
FULL_SUPPORT,
|
||||
None,
|
||||
[
|
||||
SPEED_OFF,
|
||||
SPEED_LOW,
|
||||
SPEED_MEDIUM,
|
||||
SPEED_HIGH,
|
||||
PRESET_MODE_AUTO,
|
||||
PRESET_MODE_SMART,
|
||||
PRESET_MODE_SLEEP,
|
||||
PRESET_MODE_ON,
|
||||
],
|
||||
),
|
||||
DemoFan(
|
||||
DemoPercentageFan(
|
||||
hass,
|
||||
"fan2",
|
||||
"Ceiling Fan",
|
||||
LIMITED_SUPPORT,
|
||||
None,
|
||||
[SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH],
|
||||
),
|
||||
# These fans implement the newer model
|
||||
AsyncDemoPercentageFan(
|
||||
hass,
|
||||
"fan3",
|
||||
|
@ -64,7 +52,6 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
|||
PRESET_MODE_SLEEP,
|
||||
PRESET_MODE_ON,
|
||||
],
|
||||
None,
|
||||
),
|
||||
DemoPercentageFan(
|
||||
hass,
|
||||
|
@ -77,7 +64,6 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
|||
PRESET_MODE_SLEEP,
|
||||
PRESET_MODE_ON,
|
||||
],
|
||||
None,
|
||||
),
|
||||
AsyncDemoPercentageFan(
|
||||
hass,
|
||||
|
@ -90,7 +76,6 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
|||
PRESET_MODE_SLEEP,
|
||||
PRESET_MODE_ON,
|
||||
],
|
||||
[],
|
||||
),
|
||||
]
|
||||
)
|
||||
|
@ -111,15 +96,12 @@ class BaseDemoFan(FanEntity):
|
|||
name: str,
|
||||
supported_features: int,
|
||||
preset_modes: list[str] | None,
|
||||
speed_list: list[str] | None,
|
||||
) -> None:
|
||||
"""Initialize the entity."""
|
||||
self.hass = hass
|
||||
self._unique_id = unique_id
|
||||
self._supported_features = supported_features
|
||||
self._speed = SPEED_OFF
|
||||
self._percentage = None
|
||||
self._speed_list = speed_list
|
||||
self._preset_modes = preset_modes
|
||||
self._preset_mode = None
|
||||
self._oscillating = None
|
||||
|
@ -161,52 +143,6 @@ class BaseDemoFan(FanEntity):
|
|||
return self._supported_features
|
||||
|
||||
|
||||
class DemoFan(BaseDemoFan, FanEntity):
|
||||
"""A demonstration fan component that uses legacy fan speeds."""
|
||||
|
||||
@property
|
||||
def speed(self) -> str:
|
||||
"""Return the current speed."""
|
||||
return self._speed
|
||||
|
||||
@property
|
||||
def speed_list(self):
|
||||
"""Return the speed list."""
|
||||
return self._speed_list
|
||||
|
||||
def turn_on(
|
||||
self,
|
||||
speed: str = None,
|
||||
percentage: int = None,
|
||||
preset_mode: str = None,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
"""Turn on the entity."""
|
||||
if speed is None:
|
||||
speed = SPEED_MEDIUM
|
||||
self.set_speed(speed)
|
||||
|
||||
def turn_off(self, **kwargs) -> None:
|
||||
"""Turn off the entity."""
|
||||
self.oscillate(False)
|
||||
self.set_speed(SPEED_OFF)
|
||||
|
||||
def set_speed(self, speed: str) -> None:
|
||||
"""Set the speed of the fan."""
|
||||
self._speed = speed
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
def set_direction(self, direction: str) -> None:
|
||||
"""Set the direction of the fan."""
|
||||
self._direction = direction
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
def oscillate(self, oscillating: bool) -> None:
|
||||
"""Set oscillation."""
|
||||
self._oscillating = oscillating
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
|
||||
class DemoPercentageFan(BaseDemoFan, FanEntity):
|
||||
"""A demonstration fan component that uses percentages."""
|
||||
|
||||
|
@ -238,7 +174,7 @@ class DemoPercentageFan(BaseDemoFan, FanEntity):
|
|||
|
||||
def set_preset_mode(self, preset_mode: str) -> None:
|
||||
"""Set new preset mode."""
|
||||
if preset_mode in self.preset_modes:
|
||||
if self.preset_modes and preset_mode in self.preset_modes:
|
||||
self._preset_mode = preset_mode
|
||||
self._percentage = None
|
||||
self.schedule_update_ha_state()
|
||||
|
@ -266,6 +202,16 @@ class DemoPercentageFan(BaseDemoFan, FanEntity):
|
|||
"""Turn off the entity."""
|
||||
self.set_percentage(0)
|
||||
|
||||
def set_direction(self, direction: str) -> None:
|
||||
"""Set the direction of the fan."""
|
||||
self._direction = direction
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
def oscillate(self, oscillating: bool) -> None:
|
||||
"""Set oscillation."""
|
||||
self._oscillating = oscillating
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
|
||||
class AsyncDemoPercentageFan(BaseDemoFan, FanEntity):
|
||||
"""An async demonstration fan component that uses percentages."""
|
||||
|
|
|
@ -72,32 +72,7 @@ ATTR_DIRECTION = "direction"
|
|||
ATTR_PRESET_MODE = "preset_mode"
|
||||
ATTR_PRESET_MODES = "preset_modes"
|
||||
|
||||
# Invalid speeds do not conform to the entity model, but have crept
|
||||
# into core integrations at some point so we are temporarily
|
||||
# accommodating them in the transition to percentages.
|
||||
_NOT_SPEED_OFF = "off"
|
||||
_NOT_SPEED_ON = "on"
|
||||
_NOT_SPEED_AUTO = "auto"
|
||||
_NOT_SPEED_SMART = "smart"
|
||||
_NOT_SPEED_INTERVAL = "interval"
|
||||
_NOT_SPEED_IDLE = "idle"
|
||||
_NOT_SPEED_FAVORITE = "favorite"
|
||||
_NOT_SPEED_SLEEP = "sleep"
|
||||
_NOT_SPEED_SILENT = "silent"
|
||||
|
||||
_NOT_SPEEDS_FILTER = {
|
||||
_NOT_SPEED_OFF,
|
||||
_NOT_SPEED_ON,
|
||||
_NOT_SPEED_AUTO,
|
||||
_NOT_SPEED_SMART,
|
||||
_NOT_SPEED_INTERVAL,
|
||||
_NOT_SPEED_IDLE,
|
||||
_NOT_SPEED_SILENT,
|
||||
_NOT_SPEED_SLEEP,
|
||||
_NOT_SPEED_FAVORITE,
|
||||
}
|
||||
|
||||
_FAN_NATIVE = "_fan_native"
|
||||
|
||||
OFF_SPEED_VALUES = [SPEED_OFF, None]
|
||||
|
||||
|
@ -220,12 +195,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
return await component.async_unload_entry(entry)
|
||||
|
||||
|
||||
def _fan_native(method):
|
||||
"""Native fan method not overridden."""
|
||||
setattr(method, _FAN_NATIVE, True)
|
||||
return method
|
||||
|
||||
|
||||
@dataclass
|
||||
class FanEntityDescription(ToggleEntityDescription):
|
||||
"""A class that describes fan entities."""
|
||||
|
@ -243,19 +212,17 @@ class FanEntity(ToggleEntity):
|
|||
_attr_speed_count: int
|
||||
_attr_supported_features: int = 0
|
||||
|
||||
@_fan_native
|
||||
def set_speed(self, speed: str) -> None:
|
||||
"""Set the speed of the fan."""
|
||||
raise NotImplementedError()
|
||||
|
||||
async def async_set_speed_deprecated(self, speed: str):
|
||||
"""Set the speed of the fan."""
|
||||
_LOGGER.warning(
|
||||
"The fan.set_speed service is deprecated, use fan.set_percentage or fan.set_preset_mode instead"
|
||||
_LOGGER.error(
|
||||
"The fan.set_speed service is deprecated and will fail in 2022.3 and later, use fan.set_percentage or fan.set_preset_mode instead"
|
||||
)
|
||||
await self.async_set_speed(speed)
|
||||
|
||||
@_fan_native
|
||||
async def async_set_speed(self, speed: str):
|
||||
"""Set the speed of the fan."""
|
||||
if speed == SPEED_OFF:
|
||||
|
@ -263,38 +230,20 @@ class FanEntity(ToggleEntity):
|
|||
return
|
||||
|
||||
if self.preset_modes and speed in self.preset_modes:
|
||||
if not hasattr(self.async_set_preset_mode, _FAN_NATIVE):
|
||||
await self.async_set_preset_mode(speed)
|
||||
return
|
||||
if not hasattr(self.set_preset_mode, _FAN_NATIVE):
|
||||
await self.hass.async_add_executor_job(self.set_preset_mode, speed)
|
||||
return
|
||||
else:
|
||||
if not hasattr(self.async_set_percentage, _FAN_NATIVE):
|
||||
await self.async_set_percentage(self.speed_to_percentage(speed))
|
||||
return
|
||||
if not hasattr(self.set_percentage, _FAN_NATIVE):
|
||||
await self.hass.async_add_executor_job(
|
||||
self.set_percentage, self.speed_to_percentage(speed)
|
||||
)
|
||||
return
|
||||
await self.async_set_preset_mode(speed)
|
||||
return
|
||||
|
||||
await self.hass.async_add_executor_job(self.set_speed, speed)
|
||||
await self.async_set_percentage(self.speed_to_percentage(speed))
|
||||
|
||||
@_fan_native
|
||||
def set_percentage(self, percentage: int) -> None:
|
||||
"""Set the speed of the fan, as a percentage."""
|
||||
raise NotImplementedError()
|
||||
|
||||
@_fan_native
|
||||
async def async_set_percentage(self, percentage: int) -> None:
|
||||
"""Set the speed of the fan, as a percentage."""
|
||||
if percentage == 0:
|
||||
await self.async_turn_off()
|
||||
elif not hasattr(self.set_percentage, _FAN_NATIVE):
|
||||
await self.hass.async_add_executor_job(self.set_percentage, percentage)
|
||||
else:
|
||||
await self.async_set_speed(self.percentage_to_speed(percentage))
|
||||
await self.hass.async_add_executor_job(self.set_percentage, percentage)
|
||||
|
||||
async def async_increase_speed(self, percentage_step: int | None = None) -> None:
|
||||
"""Increase the speed of the fan."""
|
||||
|
@ -325,26 +274,18 @@ class FanEntity(ToggleEntity):
|
|||
|
||||
await self.async_set_percentage(new_percentage)
|
||||
|
||||
@_fan_native
|
||||
def set_preset_mode(self, preset_mode: str) -> None:
|
||||
"""Set new preset mode."""
|
||||
self._valid_preset_mode_or_raise(preset_mode)
|
||||
self.set_speed(preset_mode)
|
||||
raise NotImplementedError()
|
||||
|
||||
@_fan_native
|
||||
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||
"""Set new preset mode."""
|
||||
if not hasattr(self.set_preset_mode, _FAN_NATIVE):
|
||||
await self.hass.async_add_executor_job(self.set_preset_mode, preset_mode)
|
||||
return
|
||||
|
||||
self._valid_preset_mode_or_raise(preset_mode)
|
||||
await self.async_set_speed(preset_mode)
|
||||
await self.hass.async_add_executor_job(self.set_preset_mode, preset_mode)
|
||||
|
||||
def _valid_preset_mode_or_raise(self, preset_mode):
|
||||
"""Raise NotValidPresetModeError on invalid preset_mode."""
|
||||
preset_modes = self.preset_modes
|
||||
if preset_mode not in preset_modes:
|
||||
if not preset_modes or preset_mode not in preset_modes:
|
||||
raise NotValidPresetModeError(
|
||||
f"The preset_mode {preset_mode} is not a valid preset_mode: {preset_modes}"
|
||||
)
|
||||
|
@ -380,16 +321,15 @@ class FanEntity(ToggleEntity):
|
|||
This _compat version wraps async_turn_on with
|
||||
backwards and forward compatibility.
|
||||
|
||||
After the transition to percentage and preset_modes concludes, it
|
||||
should be removed.
|
||||
This compatibility shim will be removed in 2022.3
|
||||
"""
|
||||
if preset_mode is not None:
|
||||
self._valid_preset_mode_or_raise(preset_mode)
|
||||
speed = preset_mode
|
||||
percentage = None
|
||||
elif speed is not None:
|
||||
_LOGGER.warning(
|
||||
"Calling fan.turn_on with the speed argument is deprecated, use percentage or preset_mode instead"
|
||||
_LOGGER.error(
|
||||
"Calling fan.turn_on with the speed argument is deprecated and will fail in 2022.3 and later, use percentage or preset_mode instead"
|
||||
)
|
||||
if self.preset_modes and speed in self.preset_modes:
|
||||
preset_mode = speed
|
||||
|
@ -441,52 +381,20 @@ class FanEntity(ToggleEntity):
|
|||
"""Return true if the entity is on."""
|
||||
return self.speed not in [SPEED_OFF, None]
|
||||
|
||||
@property
|
||||
def _implemented_percentage(self) -> bool:
|
||||
"""Return true if percentage has been implemented."""
|
||||
return not hasattr(self.set_percentage, _FAN_NATIVE) or not hasattr(
|
||||
self.async_set_percentage, _FAN_NATIVE
|
||||
)
|
||||
|
||||
@property
|
||||
def _implemented_preset_mode(self) -> bool:
|
||||
"""Return true if preset_mode has been implemented."""
|
||||
return not hasattr(self.set_preset_mode, _FAN_NATIVE) or not hasattr(
|
||||
self.async_set_preset_mode, _FAN_NATIVE
|
||||
)
|
||||
|
||||
@property
|
||||
def _implemented_speed(self) -> bool:
|
||||
"""Return true if speed has been implemented."""
|
||||
return not hasattr(self.set_speed, _FAN_NATIVE) or not hasattr(
|
||||
self.async_set_speed, _FAN_NATIVE
|
||||
)
|
||||
|
||||
@property
|
||||
def speed(self) -> str | None:
|
||||
"""Return the current speed."""
|
||||
if self._implemented_preset_mode and (preset_mode := self.preset_mode):
|
||||
if preset_mode := self.preset_mode:
|
||||
return preset_mode
|
||||
if self._implemented_percentage:
|
||||
if (percentage := self.percentage) is None:
|
||||
return None
|
||||
return self.percentage_to_speed(percentage)
|
||||
return None
|
||||
if (percentage := self.percentage) is None:
|
||||
return None
|
||||
return self.percentage_to_speed(percentage)
|
||||
|
||||
@property
|
||||
def percentage(self) -> int | None:
|
||||
"""Return the current speed as a percentage."""
|
||||
if hasattr(self, "_attr_percentage"):
|
||||
return self._attr_percentage
|
||||
|
||||
if (
|
||||
not self._implemented_preset_mode
|
||||
and self.preset_modes
|
||||
and self.speed in self.preset_modes
|
||||
):
|
||||
return None
|
||||
if self.speed is not None and not self._implemented_percentage:
|
||||
return self.speed_to_percentage(self.speed)
|
||||
return 0
|
||||
|
||||
@property
|
||||
|
@ -494,10 +402,6 @@ class FanEntity(ToggleEntity):
|
|||
"""Return the number of speeds the fan supports."""
|
||||
if hasattr(self, "_attr_speed_count"):
|
||||
return self._attr_speed_count
|
||||
|
||||
speed_list = speed_list_without_preset_modes(self.speed_list)
|
||||
if speed_list:
|
||||
return len(speed_list)
|
||||
return 100
|
||||
|
||||
@property
|
||||
|
@ -508,11 +412,9 @@ class FanEntity(ToggleEntity):
|
|||
@property
|
||||
def speed_list(self) -> list:
|
||||
"""Get the list of available speeds."""
|
||||
speeds = []
|
||||
if self._implemented_percentage:
|
||||
speeds += [SPEED_OFF, *LEGACY_SPEED_LIST]
|
||||
if self._implemented_preset_mode and self.preset_modes:
|
||||
speeds += self.preset_modes
|
||||
speeds = [SPEED_OFF, *LEGACY_SPEED_LIST]
|
||||
if preset_modes := self.preset_modes:
|
||||
speeds.extend(preset_modes)
|
||||
return speeds
|
||||
|
||||
@property
|
||||
|
@ -540,78 +442,21 @@ class FanEntity(ToggleEntity):
|
|||
|
||||
return attrs
|
||||
|
||||
@property
|
||||
def _speed_list_without_preset_modes(self) -> list:
|
||||
"""Return the speed list without preset modes.
|
||||
|
||||
This property provides forward and backwards
|
||||
compatibility for conversion to percentage speeds.
|
||||
"""
|
||||
if not self._implemented_speed:
|
||||
return LEGACY_SPEED_LIST
|
||||
return speed_list_without_preset_modes(self.speed_list)
|
||||
|
||||
def speed_to_percentage(self, speed: str) -> int:
|
||||
"""
|
||||
Map a speed to a percentage.
|
||||
|
||||
Officially this should only have to deal with the 4 pre-defined speeds:
|
||||
|
||||
return {
|
||||
SPEED_OFF: 0,
|
||||
SPEED_LOW: 33,
|
||||
SPEED_MEDIUM: 66,
|
||||
SPEED_HIGH: 100,
|
||||
}[speed]
|
||||
|
||||
Unfortunately lots of fans make up their own speeds. So the default
|
||||
mapping is more dynamic.
|
||||
"""
|
||||
def speed_to_percentage(self, speed: str) -> int: # pylint: disable=no-self-use
|
||||
"""Map a legacy speed to a percentage."""
|
||||
if speed in OFF_SPEED_VALUES:
|
||||
return 0
|
||||
|
||||
speed_list = self._speed_list_without_preset_modes
|
||||
|
||||
if speed_list and speed not in speed_list:
|
||||
if speed not in LEGACY_SPEED_LIST:
|
||||
raise NotValidSpeedError(f"The speed {speed} is not a valid speed.")
|
||||
return ordered_list_item_to_percentage(LEGACY_SPEED_LIST, speed)
|
||||
|
||||
try:
|
||||
return ordered_list_item_to_percentage(speed_list, speed)
|
||||
except ValueError as ex:
|
||||
raise NoValidSpeedsError(
|
||||
f"The speed_list {speed_list} does not contain any valid speeds."
|
||||
) from ex
|
||||
|
||||
def percentage_to_speed(self, percentage: int) -> str:
|
||||
"""
|
||||
Map a percentage onto self.speed_list.
|
||||
|
||||
Officially, this should only have to deal with 4 pre-defined speeds.
|
||||
|
||||
if value == 0:
|
||||
return SPEED_OFF
|
||||
elif value <= 33:
|
||||
return SPEED_LOW
|
||||
elif value <= 66:
|
||||
return SPEED_MEDIUM
|
||||
else:
|
||||
return SPEED_HIGH
|
||||
|
||||
Unfortunately there is currently a high degree of non-conformancy.
|
||||
Until fans have been corrected a more complicated and dynamic
|
||||
mapping is used.
|
||||
"""
|
||||
def percentage_to_speed( # pylint: disable=no-self-use
|
||||
self, percentage: int
|
||||
) -> str:
|
||||
"""Map a percentage to a legacy speed."""
|
||||
if percentage == 0:
|
||||
return SPEED_OFF
|
||||
|
||||
speed_list = self._speed_list_without_preset_modes
|
||||
|
||||
try:
|
||||
return percentage_to_ordered_list_item(speed_list, percentage)
|
||||
except ValueError as ex:
|
||||
raise NoValidSpeedsError(
|
||||
f"The speed_list {speed_list} does not contain any valid speeds."
|
||||
) from ex
|
||||
return percentage_to_ordered_list_item(LEGACY_SPEED_LIST, percentage)
|
||||
|
||||
@final
|
||||
@property
|
||||
|
@ -652,10 +497,6 @@ class FanEntity(ToggleEntity):
|
|||
"""
|
||||
if hasattr(self, "_attr_preset_mode"):
|
||||
return self._attr_preset_mode
|
||||
|
||||
speed = self.speed
|
||||
if self.preset_modes and speed in self.preset_modes:
|
||||
return speed
|
||||
return None
|
||||
|
||||
@property
|
||||
|
@ -666,55 +507,4 @@ class FanEntity(ToggleEntity):
|
|||
"""
|
||||
if hasattr(self, "_attr_preset_modes"):
|
||||
return self._attr_preset_modes
|
||||
|
||||
return preset_modes_from_speed_list(self.speed_list)
|
||||
|
||||
|
||||
def speed_list_without_preset_modes(speed_list: list):
|
||||
"""Filter out non-speeds from the speed list.
|
||||
|
||||
The goal is to get the speeds in a list from lowest to
|
||||
highest by removing speeds that are not valid or out of order
|
||||
so we can map them to percentages.
|
||||
|
||||
Examples:
|
||||
input: ["off", "low", "low-medium", "medium", "medium-high", "high", "auto"]
|
||||
output: ["low", "low-medium", "medium", "medium-high", "high"]
|
||||
|
||||
input: ["off", "auto", "low", "medium", "high"]
|
||||
output: ["low", "medium", "high"]
|
||||
|
||||
input: ["off", "1", "2", "3", "4", "5", "6", "7", "smart"]
|
||||
output: ["1", "2", "3", "4", "5", "6", "7"]
|
||||
|
||||
input: ["Auto", "Silent", "Favorite", "Idle", "Medium", "High", "Strong"]
|
||||
output: ["Medium", "High", "Strong"]
|
||||
"""
|
||||
|
||||
return [speed for speed in speed_list if speed.lower() not in _NOT_SPEEDS_FILTER]
|
||||
|
||||
|
||||
def preset_modes_from_speed_list(speed_list: list):
|
||||
"""Filter out non-preset modes from the speed list.
|
||||
|
||||
The goal is to return only preset modes.
|
||||
|
||||
Examples:
|
||||
input: ["off", "low", "low-medium", "medium", "medium-high", "high", "auto"]
|
||||
output: ["auto"]
|
||||
|
||||
input: ["off", "auto", "low", "medium", "high"]
|
||||
output: ["auto"]
|
||||
|
||||
input: ["off", "1", "2", "3", "4", "5", "6", "7", "smart"]
|
||||
output: ["smart"]
|
||||
|
||||
input: ["Auto", "Silent", "Favorite", "Idle", "Medium", "High", "Strong"]
|
||||
output: ["Auto", "Silent", "Favorite", "Idle"]
|
||||
"""
|
||||
|
||||
return [
|
||||
speed
|
||||
for speed in speed_list
|
||||
if speed.lower() in _NOT_SPEEDS_FILTER and speed.lower() != SPEED_OFF
|
||||
]
|
||||
return None
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
"""Support for Template fans."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
@ -12,16 +14,11 @@ from homeassistant.components.fan import (
|
|||
DIRECTION_FORWARD,
|
||||
DIRECTION_REVERSE,
|
||||
ENTITY_ID_FORMAT,
|
||||
SPEED_HIGH,
|
||||
SPEED_LOW,
|
||||
SPEED_MEDIUM,
|
||||
SPEED_OFF,
|
||||
SUPPORT_DIRECTION,
|
||||
SUPPORT_OSCILLATE,
|
||||
SUPPORT_PRESET_MODE,
|
||||
SUPPORT_SET_SPEED,
|
||||
FanEntity,
|
||||
preset_modes_from_speed_list,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONF_ENTITY_ID,
|
||||
|
@ -45,10 +42,8 @@ from .template_entity import TemplateEntity
|
|||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_FANS = "fans"
|
||||
CONF_SPEED_LIST = "speeds"
|
||||
CONF_SPEED_COUNT = "speed_count"
|
||||
CONF_PRESET_MODES = "preset_modes"
|
||||
CONF_SPEED_TEMPLATE = "speed_template"
|
||||
CONF_PERCENTAGE_TEMPLATE = "percentage_template"
|
||||
CONF_PRESET_MODE_TEMPLATE = "preset_mode_template"
|
||||
CONF_OSCILLATING_TEMPLATE = "oscillating_template"
|
||||
|
@ -67,14 +62,10 @@ _VALID_DIRECTIONS = [DIRECTION_FORWARD, DIRECTION_REVERSE]
|
|||
|
||||
FAN_SCHEMA = vol.All(
|
||||
cv.deprecated(CONF_ENTITY_ID),
|
||||
cv.deprecated(CONF_SPEED_LIST),
|
||||
cv.deprecated(CONF_SPEED_TEMPLATE),
|
||||
cv.deprecated(CONF_SET_SPEED_ACTION),
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_FRIENDLY_NAME): cv.string,
|
||||
vol.Required(CONF_VALUE_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_SPEED_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_PERCENTAGE_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_PRESET_MODE_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_OSCILLATING_TEMPLATE): cv.template,
|
||||
|
@ -82,16 +73,11 @@ FAN_SCHEMA = vol.All(
|
|||
vol.Optional(CONF_AVAILABILITY_TEMPLATE): cv.template,
|
||||
vol.Required(CONF_ON_ACTION): cv.SCRIPT_SCHEMA,
|
||||
vol.Required(CONF_OFF_ACTION): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(CONF_SET_SPEED_ACTION): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(CONF_SET_PERCENTAGE_ACTION): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(CONF_SET_PRESET_MODE_ACTION): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(CONF_SET_OSCILLATING_ACTION): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(CONF_SET_DIRECTION_ACTION): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(CONF_SPEED_COUNT): vol.Coerce(int),
|
||||
vol.Optional(
|
||||
CONF_SPEED_LIST,
|
||||
default=[SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH],
|
||||
): cv.ensure_list,
|
||||
vol.Optional(CONF_PRESET_MODES): cv.ensure_list,
|
||||
vol.Optional(CONF_ENTITY_ID): cv.entity_ids,
|
||||
vol.Optional(CONF_UNIQUE_ID): cv.string,
|
||||
|
@ -112,7 +98,6 @@ async def _async_create_entities(hass, config):
|
|||
friendly_name = device_config.get(CONF_FRIENDLY_NAME, device)
|
||||
|
||||
state_template = device_config[CONF_VALUE_TEMPLATE]
|
||||
speed_template = device_config.get(CONF_SPEED_TEMPLATE)
|
||||
percentage_template = device_config.get(CONF_PERCENTAGE_TEMPLATE)
|
||||
preset_mode_template = device_config.get(CONF_PRESET_MODE_TEMPLATE)
|
||||
oscillating_template = device_config.get(CONF_OSCILLATING_TEMPLATE)
|
||||
|
@ -127,7 +112,6 @@ async def _async_create_entities(hass, config):
|
|||
set_oscillating_action = device_config.get(CONF_SET_OSCILLATING_ACTION)
|
||||
set_direction_action = device_config.get(CONF_SET_DIRECTION_ACTION)
|
||||
|
||||
speed_list = device_config[CONF_SPEED_LIST]
|
||||
speed_count = device_config.get(CONF_SPEED_COUNT)
|
||||
preset_modes = device_config.get(CONF_PRESET_MODES)
|
||||
unique_id = device_config.get(CONF_UNIQUE_ID)
|
||||
|
@ -138,7 +122,6 @@ async def _async_create_entities(hass, config):
|
|||
device,
|
||||
friendly_name,
|
||||
state_template,
|
||||
speed_template,
|
||||
percentage_template,
|
||||
preset_mode_template,
|
||||
oscillating_template,
|
||||
|
@ -152,7 +135,6 @@ async def _async_create_entities(hass, config):
|
|||
set_oscillating_action,
|
||||
set_direction_action,
|
||||
speed_count,
|
||||
speed_list,
|
||||
preset_modes,
|
||||
unique_id,
|
||||
)
|
||||
|
@ -175,7 +157,6 @@ class TemplateFan(TemplateEntity, FanEntity):
|
|||
device_id,
|
||||
friendly_name,
|
||||
state_template,
|
||||
speed_template,
|
||||
percentage_template,
|
||||
preset_mode_template,
|
||||
oscillating_template,
|
||||
|
@ -189,7 +170,6 @@ class TemplateFan(TemplateEntity, FanEntity):
|
|||
set_oscillating_action,
|
||||
set_direction_action,
|
||||
speed_count,
|
||||
speed_list,
|
||||
preset_modes,
|
||||
unique_id,
|
||||
):
|
||||
|
@ -202,7 +182,6 @@ class TemplateFan(TemplateEntity, FanEntity):
|
|||
self._name = friendly_name
|
||||
|
||||
self._template = state_template
|
||||
self._speed_template = speed_template
|
||||
self._percentage_template = percentage_template
|
||||
self._preset_mode_template = preset_mode_template
|
||||
self._oscillating_template = oscillating_template
|
||||
|
@ -243,13 +222,12 @@ class TemplateFan(TemplateEntity, FanEntity):
|
|||
)
|
||||
|
||||
self._state = STATE_OFF
|
||||
self._speed = None
|
||||
self._percentage = None
|
||||
self._preset_mode = None
|
||||
self._oscillating = None
|
||||
self._direction = None
|
||||
|
||||
if self._speed_template or self._percentage_template:
|
||||
if self._percentage_template:
|
||||
self._supported_features |= SUPPORT_SET_SPEED
|
||||
if self._preset_mode_template and preset_modes:
|
||||
self._supported_features |= SUPPORT_PRESET_MODE
|
||||
|
@ -263,17 +241,9 @@ class TemplateFan(TemplateEntity, FanEntity):
|
|||
# Number of valid speeds
|
||||
self._speed_count = speed_count
|
||||
|
||||
# List of valid speeds
|
||||
self._speed_list = speed_list
|
||||
|
||||
# List of valid preset modes
|
||||
self._preset_modes = preset_modes
|
||||
|
||||
@property
|
||||
def _implemented_speed(self):
|
||||
"""Return true if speed has been implemented."""
|
||||
return bool(self._set_speed_script or self._speed_template)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the display name of this fan."""
|
||||
|
@ -295,27 +265,15 @@ class TemplateFan(TemplateEntity, FanEntity):
|
|||
return self._speed_count or 100
|
||||
|
||||
@property
|
||||
def speed_list(self) -> list:
|
||||
"""Get the list of available speeds."""
|
||||
return self._speed_list
|
||||
|
||||
@property
|
||||
def preset_modes(self) -> list:
|
||||
def preset_modes(self) -> list[str]:
|
||||
"""Get the list of available preset modes."""
|
||||
if self._preset_modes is not None:
|
||||
return self._preset_modes
|
||||
return preset_modes_from_speed_list(self._speed_list)
|
||||
return self._preset_modes
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if device is on."""
|
||||
return self._state == STATE_ON
|
||||
|
||||
@property
|
||||
def speed(self):
|
||||
"""Return the current speed."""
|
||||
return self._speed
|
||||
|
||||
@property
|
||||
def preset_mode(self):
|
||||
"""Return the current preset mode."""
|
||||
|
@ -366,30 +324,12 @@ class TemplateFan(TemplateEntity, FanEntity):
|
|||
"""Turn off the fan."""
|
||||
await self._off_script.async_run(context=self._context)
|
||||
self._state = STATE_OFF
|
||||
|
||||
async def async_set_speed(self, speed: str) -> None:
|
||||
"""Set the speed of the fan."""
|
||||
if speed not in self.speed_list:
|
||||
_LOGGER.error(
|
||||
"Received invalid speed: %s. Expected: %s", speed, self.speed_list
|
||||
)
|
||||
return
|
||||
|
||||
self._state = STATE_OFF if speed == SPEED_OFF else STATE_ON
|
||||
self._speed = speed
|
||||
self._percentage = 0
|
||||
self._preset_mode = None
|
||||
self._percentage = self.speed_to_percentage(speed)
|
||||
|
||||
if self._set_speed_script:
|
||||
await self._set_speed_script.async_run(
|
||||
{ATTR_SPEED: self._speed}, context=self._context
|
||||
)
|
||||
|
||||
async def async_set_percentage(self, percentage: int) -> None:
|
||||
"""Set the percentage speed of the fan."""
|
||||
speed_list = self.speed_list
|
||||
self._state = STATE_OFF if percentage == 0 else STATE_ON
|
||||
self._speed = self.percentage_to_speed(percentage) if speed_list else None
|
||||
self._percentage = percentage
|
||||
self._preset_mode = None
|
||||
|
||||
|
@ -400,7 +340,7 @@ class TemplateFan(TemplateEntity, FanEntity):
|
|||
|
||||
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||
"""Set the preset_mode of the fan."""
|
||||
if preset_mode not in self.preset_modes:
|
||||
if self.preset_modes and preset_mode not in self.preset_modes:
|
||||
_LOGGER.error(
|
||||
"Received invalid preset_mode: %s. Expected: %s",
|
||||
preset_mode,
|
||||
|
@ -410,7 +350,6 @@ class TemplateFan(TemplateEntity, FanEntity):
|
|||
|
||||
self._state = STATE_ON
|
||||
self._preset_mode = preset_mode
|
||||
self._speed = preset_mode
|
||||
self._percentage = None
|
||||
|
||||
if self._set_preset_mode_script:
|
||||
|
@ -491,14 +430,6 @@ class TemplateFan(TemplateEntity, FanEntity):
|
|||
self._update_percentage,
|
||||
none_on_template_error=True,
|
||||
)
|
||||
if self._speed_template is not None:
|
||||
self.add_template_attribute(
|
||||
"_speed",
|
||||
self._speed_template,
|
||||
None,
|
||||
self._update_speed,
|
||||
none_on_template_error=True,
|
||||
)
|
||||
if self._oscillating_template is not None:
|
||||
self.add_template_attribute(
|
||||
"_oscillating",
|
||||
|
@ -517,27 +448,6 @@ class TemplateFan(TemplateEntity, FanEntity):
|
|||
)
|
||||
await super().async_added_to_hass()
|
||||
|
||||
@callback
|
||||
def _update_speed(self, speed):
|
||||
# Validate speed
|
||||
speed = str(speed)
|
||||
|
||||
if speed in self._speed_list:
|
||||
self._speed = speed
|
||||
self._percentage = self.speed_to_percentage(speed)
|
||||
self._preset_mode = speed if speed in self.preset_modes else None
|
||||
elif speed in (STATE_UNAVAILABLE, STATE_UNKNOWN):
|
||||
self._speed = None
|
||||
self._percentage = 0
|
||||
self._preset_mode = None
|
||||
else:
|
||||
_LOGGER.error(
|
||||
"Received invalid speed: %s. Expected: %s", speed, self._speed_list
|
||||
)
|
||||
self._speed = None
|
||||
self._percentage = 0
|
||||
self._preset_mode = None
|
||||
|
||||
@callback
|
||||
def _update_percentage(self, percentage):
|
||||
# Validate percentage
|
||||
|
@ -545,19 +455,15 @@ class TemplateFan(TemplateEntity, FanEntity):
|
|||
percentage = int(float(percentage))
|
||||
except ValueError:
|
||||
_LOGGER.error("Received invalid percentage: %s", percentage)
|
||||
self._speed = None
|
||||
self._percentage = 0
|
||||
self._preset_mode = None
|
||||
return
|
||||
|
||||
if 0 <= percentage <= 100:
|
||||
self._percentage = percentage
|
||||
if self._speed_list:
|
||||
self._speed = self.percentage_to_speed(percentage)
|
||||
self._preset_mode = None
|
||||
else:
|
||||
_LOGGER.error("Received invalid percentage: %s", percentage)
|
||||
self._speed = None
|
||||
self._percentage = 0
|
||||
self._preset_mode = None
|
||||
|
||||
|
@ -566,12 +472,10 @@ class TemplateFan(TemplateEntity, FanEntity):
|
|||
# Validate preset mode
|
||||
preset_mode = str(preset_mode)
|
||||
|
||||
if preset_mode in self.preset_modes:
|
||||
self._speed = preset_mode
|
||||
if self.preset_modes and preset_mode in self.preset_modes:
|
||||
self._percentage = None
|
||||
self._preset_mode = preset_mode
|
||||
elif preset_mode in (STATE_UNAVAILABLE, STATE_UNKNOWN):
|
||||
self._speed = None
|
||||
self._percentage = None
|
||||
self._preset_mode = None
|
||||
else:
|
||||
|
@ -580,7 +484,6 @@ class TemplateFan(TemplateEntity, FanEntity):
|
|||
preset_mode,
|
||||
self.preset_mode,
|
||||
)
|
||||
self._speed = None
|
||||
self._percentage = None
|
||||
self._preset_mode = None
|
||||
|
||||
|
|
|
@ -321,7 +321,7 @@ async def test_set_direction(hass, fan_entity_id):
|
|||
assert state.attributes[fan.ATTR_DIRECTION] == fan.DIRECTION_REVERSE
|
||||
|
||||
|
||||
@pytest.mark.parametrize("fan_entity_id", FULL_FAN_ENTITY_IDS)
|
||||
@pytest.mark.parametrize("fan_entity_id", LIMITED_AND_FULL_FAN_ENTITY_IDS)
|
||||
async def test_set_speed(hass, fan_entity_id):
|
||||
"""Test setting the speed of the device."""
|
||||
state = hass.states.get(fan_entity_id)
|
||||
|
@ -336,6 +336,34 @@ async def test_set_speed(hass, fan_entity_id):
|
|||
state = hass.states.get(fan_entity_id)
|
||||
assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_LOW
|
||||
|
||||
await hass.services.async_call(
|
||||
fan.DOMAIN,
|
||||
fan.SERVICE_SET_SPEED,
|
||||
{ATTR_ENTITY_ID: fan_entity_id, fan.ATTR_SPEED: fan.SPEED_OFF},
|
||||
blocking=True,
|
||||
)
|
||||
state = hass.states.get(fan_entity_id)
|
||||
assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_OFF
|
||||
|
||||
|
||||
@pytest.mark.parametrize("fan_entity_id", FANS_WITH_PRESET_MODES)
|
||||
async def test_set_preset_mode_with_legacy_speed_service(hass, fan_entity_id):
|
||||
"""Test setting the preset mode is possible with the legacy service for backwards compat."""
|
||||
state = hass.states.get(fan_entity_id)
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
await hass.services.async_call(
|
||||
fan.DOMAIN,
|
||||
fan.SERVICE_SET_SPEED,
|
||||
{ATTR_ENTITY_ID: fan_entity_id, fan.ATTR_SPEED: PRESET_MODE_AUTO},
|
||||
blocking=True,
|
||||
)
|
||||
state = hass.states.get(fan_entity_id)
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes[fan.ATTR_SPEED] == PRESET_MODE_AUTO
|
||||
assert state.attributes[fan.ATTR_PERCENTAGE] is None
|
||||
assert state.attributes[fan.ATTR_PRESET_MODE] == PRESET_MODE_AUTO
|
||||
|
||||
|
||||
@pytest.mark.parametrize("fan_entity_id", FANS_WITH_PRESET_MODES)
|
||||
async def test_set_preset_mode(hass, fan_entity_id):
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.fan import FanEntity, NotValidPresetModeError
|
||||
from homeassistant.components.fan import FanEntity
|
||||
|
||||
|
||||
class BaseFan(FanEntity):
|
||||
|
@ -16,8 +16,8 @@ def test_fanentity():
|
|||
"""Test fan entity methods."""
|
||||
fan = BaseFan()
|
||||
assert fan.state == "off"
|
||||
assert len(fan.speed_list) == 0
|
||||
assert len(fan.preset_modes) == 0
|
||||
assert len(fan.speed_list) == 4 # legacy compat off,low,medium,high
|
||||
assert fan.preset_modes is None
|
||||
assert fan.supported_features == 0
|
||||
assert fan.percentage_step == 1
|
||||
assert fan.speed_count == 100
|
||||
|
@ -26,10 +26,10 @@ def test_fanentity():
|
|||
with pytest.raises(NotImplementedError):
|
||||
fan.oscillate(True)
|
||||
with pytest.raises(NotImplementedError):
|
||||
fan.set_speed("slow")
|
||||
fan.set_speed("low")
|
||||
with pytest.raises(NotImplementedError):
|
||||
fan.set_percentage(0)
|
||||
with pytest.raises(NotValidPresetModeError):
|
||||
with pytest.raises(NotImplementedError):
|
||||
fan.set_preset_mode("auto")
|
||||
with pytest.raises(NotImplementedError):
|
||||
fan.turn_on()
|
||||
|
@ -42,8 +42,8 @@ async def test_async_fanentity(hass):
|
|||
fan = BaseFan()
|
||||
fan.hass = hass
|
||||
assert fan.state == "off"
|
||||
assert len(fan.speed_list) == 0
|
||||
assert len(fan.preset_modes) == 0
|
||||
assert len(fan.speed_list) == 4 # legacy compat off,low,medium,high
|
||||
assert fan.preset_modes is None
|
||||
assert fan.supported_features == 0
|
||||
assert fan.percentage_step == 1
|
||||
assert fan.speed_count == 100
|
||||
|
@ -52,10 +52,10 @@ async def test_async_fanentity(hass):
|
|||
with pytest.raises(NotImplementedError):
|
||||
await fan.async_oscillate(True)
|
||||
with pytest.raises(NotImplementedError):
|
||||
await fan.async_set_speed("slow")
|
||||
await fan.async_set_speed("low")
|
||||
with pytest.raises(NotImplementedError):
|
||||
await fan.async_set_percentage(0)
|
||||
with pytest.raises(NotValidPresetModeError):
|
||||
with pytest.raises(NotImplementedError):
|
||||
await fan.async_set_preset_mode("auto")
|
||||
with pytest.raises(NotImplementedError):
|
||||
await fan.async_turn_on()
|
||||
|
|
|
@ -18,6 +18,7 @@ from homeassistant.components.fan import (
|
|||
SPEED_OFF,
|
||||
SUPPORT_PRESET_MODE,
|
||||
SUPPORT_SET_SPEED,
|
||||
NotValidSpeedError,
|
||||
)
|
||||
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE
|
||||
|
||||
|
@ -29,8 +30,6 @@ _TEST_FAN = "fan.test_fan"
|
|||
_STATE_INPUT_BOOLEAN = "input_boolean.state"
|
||||
# Represent for fan's state
|
||||
_STATE_AVAILABILITY_BOOLEAN = "availability_boolean.state"
|
||||
# Represent for fan's speed
|
||||
_SPEED_INPUT_SELECT = "input_select.speed"
|
||||
# Represent for fan's preset mode
|
||||
_PRESET_MODE_INPUT_SELECT = "input_select.preset_mode"
|
||||
# Represent for fan's speed percentage
|
||||
|
@ -148,7 +147,6 @@ async def test_wrong_template_config(hass, start_ha):
|
|||
{% endif %}
|
||||
""",
|
||||
"percentage_template": "{{ states('input_number.percentage') }}",
|
||||
"speed_template": "{{ states('input_select.speed') }}",
|
||||
"preset_mode_template": "{{ states('input_select.preset_mode') }}",
|
||||
"oscillating_template": "{{ states('input_select.osc') }}",
|
||||
"direction_template": "{{ states('input_select.direction') }}",
|
||||
|
@ -170,7 +168,7 @@ async def test_templates_with_entities(hass, start_ha):
|
|||
_verify(hass, STATE_OFF, None, 0, None, None, None)
|
||||
|
||||
hass.states.async_set(_STATE_INPUT_BOOLEAN, True)
|
||||
hass.states.async_set(_SPEED_INPUT_SELECT, SPEED_MEDIUM)
|
||||
hass.states.async_set(_PERCENTAGE_INPUT_NUMBER, 66)
|
||||
hass.states.async_set(_OSC_INPUT, "True")
|
||||
|
||||
for set_state, set_value, speed, value in [
|
||||
|
@ -262,7 +260,6 @@ async def test_templates_with_entities2(hass, entity, tests, start_ha):
|
|||
"test_fan": {
|
||||
"availability_template": "{{ is_state('availability_boolean.state', 'on') }}",
|
||||
"value_template": "{{ 'on' }}",
|
||||
"speed_template": "{{ 'medium' }}",
|
||||
"oscillating_template": "{{ 1 == 1 }}",
|
||||
"direction_template": "{{ 'forward' }}",
|
||||
"turn_on": {"service": "script.fan_on"},
|
||||
|
@ -307,9 +304,9 @@ async def test_availability_template_with_entities(hass, start_ha):
|
|||
"fans": {
|
||||
"test_fan": {
|
||||
"value_template": "{{ 'on' }}",
|
||||
"speed_template": "{{ 'unavailable' }}",
|
||||
"oscillating_template": "{{ 'unavailable' }}",
|
||||
"direction_template": "{{ 'unavailable' }}",
|
||||
"percentage_template": "{{ 0 }}",
|
||||
"turn_on": {"service": "script.fan_on"},
|
||||
"turn_off": {"service": "script.fan_off"},
|
||||
}
|
||||
|
@ -325,9 +322,9 @@ async def test_availability_template_with_entities(hass, start_ha):
|
|||
"fans": {
|
||||
"test_fan": {
|
||||
"value_template": "{{ 'on' }}",
|
||||
"speed_template": "{{ 'medium' }}",
|
||||
"oscillating_template": "{{ 1 == 1 }}",
|
||||
"direction_template": "{{ 'forward' }}",
|
||||
"percentage_template": "{{ 66 }}",
|
||||
"turn_on": {"service": "script.fan_on"},
|
||||
"turn_off": {"service": "script.fan_off"},
|
||||
}
|
||||
|
@ -343,9 +340,9 @@ async def test_availability_template_with_entities(hass, start_ha):
|
|||
"fans": {
|
||||
"test_fan": {
|
||||
"value_template": "{{ 'abc' }}",
|
||||
"speed_template": "{{ '0' }}",
|
||||
"oscillating_template": "{{ 'xyz' }}",
|
||||
"direction_template": "{{ 'right' }}",
|
||||
"percentage_template": "{{ 0 }}",
|
||||
"turn_on": {"service": "script.fan_on"},
|
||||
"turn_off": {"service": "script.fan_off"},
|
||||
}
|
||||
|
@ -372,7 +369,6 @@ async def test_template_with_unavailable_entities(hass, states, start_ha):
|
|||
"test_fan": {
|
||||
"value_template": "{{ 'on' }}",
|
||||
"availability_template": "{{ x - 12 }}",
|
||||
"speed_template": "{{ states('input_select.speed') }}",
|
||||
"preset_mode_template": "{{ states('input_select.preset_mode') }}",
|
||||
"oscillating_template": "{{ states('input_select.osc') }}",
|
||||
"direction_template": "{{ states('input_select.direction') }}",
|
||||
|
@ -411,38 +407,34 @@ async def test_set_speed(hass):
|
|||
await _register_components(hass, preset_modes=["auto", "smart"])
|
||||
|
||||
await common.async_turn_on(hass, _TEST_FAN)
|
||||
for cmd, t_state, type, state, value in [
|
||||
(SPEED_HIGH, SPEED_HIGH, SPEED_HIGH, STATE_ON, 100),
|
||||
(SPEED_MEDIUM, SPEED_MEDIUM, SPEED_MEDIUM, STATE_ON, 66),
|
||||
(SPEED_OFF, SPEED_OFF, SPEED_OFF, STATE_OFF, 0),
|
||||
(SPEED_MEDIUM, SPEED_MEDIUM, SPEED_MEDIUM, STATE_ON, 66),
|
||||
("invalid", SPEED_MEDIUM, SPEED_MEDIUM, STATE_ON, 66),
|
||||
for cmd, type, state, value in [
|
||||
(SPEED_HIGH, SPEED_HIGH, STATE_ON, 100),
|
||||
(SPEED_MEDIUM, SPEED_MEDIUM, STATE_ON, 66),
|
||||
(SPEED_OFF, SPEED_OFF, STATE_OFF, 0),
|
||||
(SPEED_MEDIUM, SPEED_MEDIUM, STATE_ON, 66),
|
||||
]:
|
||||
await common.async_set_speed(hass, _TEST_FAN, cmd)
|
||||
assert hass.states.get(_SPEED_INPUT_SELECT).state == t_state
|
||||
assert float(hass.states.get(_PERCENTAGE_INPUT_NUMBER).state) == value
|
||||
_verify(hass, state, type, value, None, None, None)
|
||||
|
||||
with pytest.raises(NotValidSpeedError):
|
||||
await common.async_set_speed(hass, _TEST_FAN, "invalid")
|
||||
|
||||
assert float(hass.states.get(_PERCENTAGE_INPUT_NUMBER).state) == 66
|
||||
_verify(hass, STATE_ON, SPEED_MEDIUM, 66, None, None, None)
|
||||
|
||||
|
||||
async def test_set_invalid_speed(hass):
|
||||
"""Test set invalid speed when fan has valid speed."""
|
||||
await _register_components(hass)
|
||||
|
||||
await common.async_turn_on(hass, _TEST_FAN)
|
||||
for extra in [SPEED_HIGH, "invalid"]:
|
||||
await common.async_set_speed(hass, _TEST_FAN, extra)
|
||||
assert hass.states.get(_SPEED_INPUT_SELECT).state == SPEED_HIGH
|
||||
_verify(hass, STATE_ON, SPEED_HIGH, 100, None, None, None)
|
||||
await common.async_set_speed(hass, _TEST_FAN, SPEED_HIGH)
|
||||
assert float(hass.states.get(_PERCENTAGE_INPUT_NUMBER).state) == 100
|
||||
_verify(hass, STATE_ON, SPEED_HIGH, 100, None, None, None)
|
||||
|
||||
|
||||
async def test_custom_speed_list(hass):
|
||||
"""Test set custom speed list."""
|
||||
await _register_components(hass, ["1", "2", "3"])
|
||||
|
||||
await common.async_turn_on(hass, _TEST_FAN)
|
||||
for extra in ["1", SPEED_MEDIUM]:
|
||||
await common.async_set_speed(hass, _TEST_FAN, extra)
|
||||
assert hass.states.get(_SPEED_INPUT_SELECT).state == "1"
|
||||
_verify(hass, STATE_ON, "1", 33, None, None, None)
|
||||
with pytest.raises(NotValidSpeedError):
|
||||
await common.async_set_speed(hass, _TEST_FAN, "invalid")
|
||||
|
||||
|
||||
async def test_set_invalid_direction_from_initial_stage(hass, calls):
|
||||
|
@ -610,7 +602,7 @@ def _verify(
|
|||
state = hass.states.get(_TEST_FAN)
|
||||
attributes = state.attributes
|
||||
assert state.state == str(expected_state)
|
||||
assert attributes.get(ATTR_SPEED) == expected_speed
|
||||
assert attributes.get(ATTR_SPEED) == expected_speed or SPEED_OFF
|
||||
assert attributes.get(ATTR_PERCENTAGE) == expected_percentage
|
||||
assert attributes.get(ATTR_OSCILLATING) == expected_oscillating
|
||||
assert attributes.get(ATTR_DIRECTION) == expected_direction
|
||||
|
@ -643,27 +635,12 @@ async def _register_components(
|
|||
},
|
||||
)
|
||||
|
||||
with assert_setup_component(4, "input_select"):
|
||||
with assert_setup_component(3, "input_select"):
|
||||
assert await setup.async_setup_component(
|
||||
hass,
|
||||
"input_select",
|
||||
{
|
||||
"input_select": {
|
||||
"speed": {
|
||||
"name": "Speed",
|
||||
"options": [
|
||||
"",
|
||||
SPEED_OFF,
|
||||
SPEED_LOW,
|
||||
SPEED_MEDIUM,
|
||||
SPEED_HIGH,
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"auto",
|
||||
"smart",
|
||||
],
|
||||
},
|
||||
"preset_mode": {
|
||||
"name": "Preset Mode",
|
||||
"options": ["auto", "smart"],
|
||||
|
@ -688,7 +665,6 @@ async def _register_components(
|
|||
|
||||
test_fan_config = {
|
||||
"value_template": value_template,
|
||||
"speed_template": "{{ states('input_select.speed') }}",
|
||||
"preset_mode_template": "{{ states('input_select.preset_mode') }}",
|
||||
"percentage_template": "{{ states('input_number.percentage') }}",
|
||||
"oscillating_template": "{{ states('input_select.osc') }}",
|
||||
|
@ -697,17 +673,19 @@ async def _register_components(
|
|||
"service": "input_boolean.turn_on",
|
||||
"entity_id": _STATE_INPUT_BOOLEAN,
|
||||
},
|
||||
"turn_off": {
|
||||
"service": "input_boolean.turn_off",
|
||||
"entity_id": _STATE_INPUT_BOOLEAN,
|
||||
},
|
||||
"set_speed": {
|
||||
"service": "input_select.select_option",
|
||||
"data_template": {
|
||||
"entity_id": _SPEED_INPUT_SELECT,
|
||||
"option": "{{ speed }}",
|
||||
"turn_off": [
|
||||
{
|
||||
"service": "input_boolean.turn_off",
|
||||
"entity_id": _STATE_INPUT_BOOLEAN,
|
||||
},
|
||||
},
|
||||
{
|
||||
"service": "input_number.set_value",
|
||||
"data_template": {
|
||||
"entity_id": _PERCENTAGE_INPUT_NUMBER,
|
||||
"value": 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
"set_preset_mode": {
|
||||
"service": "input_select.select_option",
|
||||
"data_template": {
|
||||
|
@ -738,9 +716,6 @@ async def _register_components(
|
|||
},
|
||||
}
|
||||
|
||||
if speed_list:
|
||||
test_fan_config["speeds"] = speed_list
|
||||
|
||||
if preset_modes:
|
||||
test_fan_config["preset_modes"] = preset_modes
|
||||
|
||||
|
@ -940,71 +915,3 @@ async def test_implemented_preset_mode(hass, start_ha):
|
|||
attributes = state.attributes
|
||||
assert attributes.get("percentage") is None
|
||||
assert attributes.get("supported_features") & SUPPORT_PRESET_MODE
|
||||
|
||||
|
||||
@pytest.mark.parametrize("count,domain", [(1, DOMAIN)])
|
||||
@pytest.mark.parametrize(
|
||||
"config",
|
||||
[
|
||||
{
|
||||
DOMAIN: {
|
||||
"platform": "template",
|
||||
"fans": {
|
||||
"mechanical_ventilation": {
|
||||
"friendly_name": "Mechanische ventilatie",
|
||||
"unique_id": "a2fd2e38-674b-4b47-b5ef-cc2362211a72",
|
||||
"value_template": "{{ states('light.mv_snelheid') }}",
|
||||
"speed_template": "{{ 'fast' }}",
|
||||
"speeds": ["slow", "fast"],
|
||||
"set_preset_mode": [
|
||||
{
|
||||
"service": "light.turn_on",
|
||||
"target": {
|
||||
"entity_id": "light.mv_snelheid",
|
||||
},
|
||||
"data": {"brightness_pct": "{{ percentage }}"},
|
||||
}
|
||||
],
|
||||
"turn_on": [
|
||||
{
|
||||
"service": "switch.turn_off",
|
||||
"target": {
|
||||
"entity_id": "switch.mv_automatisch",
|
||||
},
|
||||
},
|
||||
{
|
||||
"service": "light.turn_on",
|
||||
"target": {
|
||||
"entity_id": "light.mv_snelheid",
|
||||
},
|
||||
"data": {"brightness_pct": 40},
|
||||
},
|
||||
],
|
||||
"turn_off": [
|
||||
{
|
||||
"service": "light.turn_off",
|
||||
"target": {
|
||||
"entity_id": "light.mv_snelheid",
|
||||
},
|
||||
},
|
||||
{
|
||||
"service": "switch.turn_on",
|
||||
"target": {
|
||||
"entity_id": "switch.mv_automatisch",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
],
|
||||
)
|
||||
async def test_implemented_speed(hass, start_ha):
|
||||
"""Test a fan that implements speed."""
|
||||
assert len(hass.states.async_all()) == 1
|
||||
|
||||
state = hass.states.get("fan.mechanical_ventilation")
|
||||
attributes = state.attributes
|
||||
assert attributes["percentage"] == 100
|
||||
assert attributes["speed"] == "fast"
|
||||
|
|
Loading…
Reference in New Issue