Separate fan speeds into percentages and presets modes (#45407)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com> Co-authored-by: John Carr <john.carr@unrouted.co.uk>pull/45628/head
parent
3f948e027a
commit
068d1b5eb8
|
@ -118,7 +118,20 @@ class BondFan(BondEntity, FanEntity):
|
|||
self._device.device_id, Action.set_speed(bond_speed)
|
||||
)
|
||||
|
||||
async def async_turn_on(self, speed: Optional[str] = None, **kwargs) -> None:
|
||||
#
|
||||
# The fan entity model has changed to use percentages and preset_modes
|
||||
# instead of speeds.
|
||||
#
|
||||
# Please review
|
||||
# https://developers.home-assistant.io/docs/core/entity/fan/
|
||||
#
|
||||
async def async_turn_on(
|
||||
self,
|
||||
speed: Optional[str] = None,
|
||||
percentage: Optional[int] = None,
|
||||
preset_mode: Optional[str] = None,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
"""Turn on the fan."""
|
||||
_LOGGER.debug("Fan async_turn_on called with speed %s", speed)
|
||||
|
||||
|
|
|
@ -102,7 +102,16 @@ class ComfoConnectFan(FanEntity):
|
|||
"""List of available fan modes."""
|
||||
return [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
|
||||
|
||||
def turn_on(self, speed: str = None, **kwargs) -> None:
|
||||
#
|
||||
# The fan entity model has changed to use percentages and preset_modes
|
||||
# instead of speeds.
|
||||
#
|
||||
# Please review
|
||||
# https://developers.home-assistant.io/docs/core/entity/fan/
|
||||
#
|
||||
def turn_on(
|
||||
self, speed: str = None, percentage=None, preset_mode=None, **kwargs
|
||||
) -> None:
|
||||
"""Turn on the fan."""
|
||||
if speed is None:
|
||||
speed = SPEED_LOW
|
||||
|
|
|
@ -107,7 +107,20 @@ class DeconzFan(DeconzDevice, FanEntity):
|
|||
|
||||
await self._device.set_speed(SPEEDS[speed])
|
||||
|
||||
async def async_turn_on(self, speed: str = None, **kwargs) -> None:
|
||||
#
|
||||
# The fan entity model has changed to use percentages and preset_modes
|
||||
# instead of speeds.
|
||||
#
|
||||
# Please review
|
||||
# https://developers.home-assistant.io/docs/core/entity/fan/
|
||||
#
|
||||
async def async_turn_on(
|
||||
self,
|
||||
speed: str = None,
|
||||
percentage: int = None,
|
||||
preset_mode: str = None,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
"""Turn on fan."""
|
||||
if not speed:
|
||||
speed = convert_speed(self._default_on_speed)
|
||||
|
|
|
@ -1,14 +1,20 @@
|
|||
"""Demo fan platform that has a fake fan."""
|
||||
from typing import List, Optional
|
||||
|
||||
from homeassistant.components.fan import (
|
||||
SPEED_HIGH,
|
||||
SPEED_LOW,
|
||||
SPEED_MEDIUM,
|
||||
SPEED_OFF,
|
||||
SUPPORT_DIRECTION,
|
||||
SUPPORT_OSCILLATE,
|
||||
SUPPORT_PRESET_MODE,
|
||||
SUPPORT_SET_SPEED,
|
||||
FanEntity,
|
||||
)
|
||||
from homeassistant.const import STATE_OFF
|
||||
|
||||
PRESET_MODE_AUTO = "auto"
|
||||
PRESET_MODE_SMART = "smart"
|
||||
|
||||
FULL_SUPPORT = SUPPORT_SET_SPEED | SUPPORT_OSCILLATE | SUPPORT_DIRECTION
|
||||
LIMITED_SUPPORT = SUPPORT_SET_SPEED
|
||||
|
@ -18,8 +24,55 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
|||
"""Set up the demo fan platform."""
|
||||
async_add_entities(
|
||||
[
|
||||
DemoFan(hass, "fan1", "Living Room Fan", FULL_SUPPORT),
|
||||
DemoFan(hass, "fan2", "Ceiling Fan", LIMITED_SUPPORT),
|
||||
# These fans implement the old model
|
||||
DemoFan(
|
||||
hass,
|
||||
"fan1",
|
||||
"Living Room Fan",
|
||||
FULL_SUPPORT,
|
||||
None,
|
||||
[
|
||||
SPEED_OFF,
|
||||
SPEED_LOW,
|
||||
SPEED_MEDIUM,
|
||||
SPEED_HIGH,
|
||||
PRESET_MODE_AUTO,
|
||||
PRESET_MODE_SMART,
|
||||
],
|
||||
),
|
||||
DemoFan(
|
||||
hass,
|
||||
"fan2",
|
||||
"Ceiling Fan",
|
||||
LIMITED_SUPPORT,
|
||||
None,
|
||||
[SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH],
|
||||
),
|
||||
# These fans implement the newer model
|
||||
AsyncDemoPercentageFan(
|
||||
hass,
|
||||
"fan3",
|
||||
"Percentage Full Fan",
|
||||
FULL_SUPPORT,
|
||||
[PRESET_MODE_AUTO, PRESET_MODE_SMART],
|
||||
None,
|
||||
),
|
||||
DemoPercentageFan(
|
||||
hass,
|
||||
"fan4",
|
||||
"Percentage Limited Fan",
|
||||
LIMITED_SUPPORT,
|
||||
[PRESET_MODE_AUTO, PRESET_MODE_SMART],
|
||||
None,
|
||||
),
|
||||
AsyncDemoPercentageFan(
|
||||
hass,
|
||||
"fan5",
|
||||
"Preset Only Limited Fan",
|
||||
SUPPORT_PRESET_MODE,
|
||||
[PRESET_MODE_AUTO, PRESET_MODE_SMART],
|
||||
[],
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
|
@ -29,21 +82,30 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||
await async_setup_platform(hass, {}, async_add_entities)
|
||||
|
||||
|
||||
class DemoFan(FanEntity):
|
||||
"""A demonstration fan component."""
|
||||
class BaseDemoFan(FanEntity):
|
||||
"""A demonstration fan component that uses legacy fan speeds."""
|
||||
|
||||
def __init__(
|
||||
self, hass, unique_id: str, name: str, supported_features: int
|
||||
self,
|
||||
hass,
|
||||
unique_id: str,
|
||||
name: str,
|
||||
supported_features: int,
|
||||
preset_modes: Optional[List[str]],
|
||||
speed_list: Optional[List[str]],
|
||||
) -> None:
|
||||
"""Initialize the entity."""
|
||||
self.hass = hass
|
||||
self._unique_id = unique_id
|
||||
self._supported_features = supported_features
|
||||
self._speed = STATE_OFF
|
||||
self._speed = SPEED_OFF
|
||||
self._percentage = 0
|
||||
self._speed_list = speed_list
|
||||
self._preset_modes = preset_modes
|
||||
self._preset_mode = None
|
||||
self._oscillating = None
|
||||
self._direction = None
|
||||
self._name = name
|
||||
|
||||
if supported_features & SUPPORT_OSCILLATE:
|
||||
self._oscillating = False
|
||||
if supported_features & SUPPORT_DIRECTION:
|
||||
|
@ -64,17 +126,42 @@ class DemoFan(FanEntity):
|
|||
"""No polling needed for a demo fan."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def current_direction(self) -> str:
|
||||
"""Fan direction."""
|
||||
return self._direction
|
||||
|
||||
@property
|
||||
def oscillating(self) -> bool:
|
||||
"""Oscillating."""
|
||||
return self._oscillating
|
||||
|
||||
@property
|
||||
def supported_features(self) -> int:
|
||||
"""Flag supported features."""
|
||||
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) -> list:
|
||||
"""Get the list of available speeds."""
|
||||
return [STATE_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
|
||||
def speed_list(self):
|
||||
"""Return the speed list."""
|
||||
return self._speed_list
|
||||
|
||||
def turn_on(self, speed: str = None, **kwargs) -> None:
|
||||
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
|
||||
|
@ -83,7 +170,7 @@ class DemoFan(FanEntity):
|
|||
def turn_off(self, **kwargs) -> None:
|
||||
"""Turn off the entity."""
|
||||
self.oscillate(False)
|
||||
self.set_speed(STATE_OFF)
|
||||
self.set_speed(SPEED_OFF)
|
||||
|
||||
def set_speed(self, speed: str) -> None:
|
||||
"""Set the speed of the fan."""
|
||||
|
@ -100,17 +187,124 @@ class DemoFan(FanEntity):
|
|||
self._oscillating = oscillating
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
@property
|
||||
def current_direction(self) -> str:
|
||||
"""Fan direction."""
|
||||
return self._direction
|
||||
|
||||
class DemoPercentageFan(BaseDemoFan, FanEntity):
|
||||
"""A demonstration fan component that uses percentages."""
|
||||
|
||||
@property
|
||||
def oscillating(self) -> bool:
|
||||
"""Oscillating."""
|
||||
return self._oscillating
|
||||
def percentage(self) -> str:
|
||||
"""Return the current speed."""
|
||||
return self._percentage
|
||||
|
||||
def set_percentage(self, percentage: int) -> None:
|
||||
"""Set the speed of the fan, as a percentage."""
|
||||
self._percentage = percentage
|
||||
self._preset_mode = None
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
@property
|
||||
def supported_features(self) -> int:
|
||||
"""Flag supported features."""
|
||||
return self._supported_features
|
||||
def preset_mode(self) -> Optional[str]:
|
||||
"""Return the current preset mode, e.g., auto, smart, interval, favorite."""
|
||||
return self._preset_mode
|
||||
|
||||
@property
|
||||
def preset_modes(self) -> Optional[List[str]]:
|
||||
"""Return a list of available preset modes."""
|
||||
return self._preset_modes
|
||||
|
||||
def set_preset_mode(self, preset_mode: str) -> None:
|
||||
"""Set new preset mode."""
|
||||
if preset_mode in self.preset_modes:
|
||||
self._preset_mode = preset_mode
|
||||
self._percentage = None
|
||||
self.schedule_update_ha_state()
|
||||
else:
|
||||
raise ValueError(f"Invalid preset mode: {preset_mode}")
|
||||
|
||||
def turn_on(
|
||||
self,
|
||||
speed: str = None,
|
||||
percentage: int = None,
|
||||
preset_mode: str = None,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
"""Turn on the entity."""
|
||||
if preset_mode:
|
||||
self.set_preset_mode(preset_mode)
|
||||
return
|
||||
|
||||
if percentage is None:
|
||||
percentage = 67
|
||||
|
||||
self.set_percentage(percentage)
|
||||
|
||||
def turn_off(self, **kwargs) -> None:
|
||||
"""Turn off the entity."""
|
||||
self.set_percentage(0)
|
||||
|
||||
|
||||
class AsyncDemoPercentageFan(BaseDemoFan, FanEntity):
|
||||
"""An async demonstration fan component that uses percentages."""
|
||||
|
||||
@property
|
||||
def percentage(self) -> str:
|
||||
"""Return the current speed."""
|
||||
return self._percentage
|
||||
|
||||
async def async_set_percentage(self, percentage: int) -> None:
|
||||
"""Set the speed of the fan, as a percentage."""
|
||||
self._percentage = percentage
|
||||
self._preset_mode = None
|
||||
self.async_write_ha_state()
|
||||
|
||||
@property
|
||||
def preset_mode(self) -> Optional[str]:
|
||||
"""Return the current preset mode, e.g., auto, smart, interval, favorite."""
|
||||
return self._preset_mode
|
||||
|
||||
@property
|
||||
def preset_modes(self) -> Optional[List[str]]:
|
||||
"""Return a list of available preset modes."""
|
||||
return self._preset_modes
|
||||
|
||||
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||
"""Set new preset mode."""
|
||||
if preset_mode not in self.preset_modes:
|
||||
raise ValueError(
|
||||
"{preset_mode} is not a valid preset_mode: {self.preset_modes}"
|
||||
)
|
||||
self._preset_mode = preset_mode
|
||||
self._percentage = None
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_turn_on(
|
||||
self,
|
||||
speed: str = None,
|
||||
percentage: int = None,
|
||||
preset_mode: str = None,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
"""Turn on the entity."""
|
||||
if preset_mode:
|
||||
await self.async_set_preset_mode(preset_mode)
|
||||
return
|
||||
|
||||
if percentage is None:
|
||||
percentage = 67
|
||||
|
||||
await self.async_set_percentage(percentage)
|
||||
|
||||
async def async_turn_off(self, **kwargs) -> None:
|
||||
"""Turn off the entity."""
|
||||
await self.async_oscillate(False)
|
||||
await self.async_set_percentage(0)
|
||||
|
||||
async def async_set_direction(self, direction: str) -> None:
|
||||
"""Set the direction of the fan."""
|
||||
self._direction = direction
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_oscillate(self, oscillating: bool) -> None:
|
||||
"""Set oscillation."""
|
||||
self._oscillating = oscillating
|
||||
self.async_write_ha_state()
|
||||
|
|
|
@ -233,7 +233,20 @@ class DysonPureCoolLinkEntity(DysonFanEntity):
|
|||
"""Initialize the fan."""
|
||||
super().__init__(device, DysonPureCoolState)
|
||||
|
||||
def turn_on(self, speed: Optional[str] = None, **kwargs) -> None:
|
||||
#
|
||||
# The fan entity model has changed to use percentages and preset_modes
|
||||
# instead of speeds.
|
||||
#
|
||||
# Please review
|
||||
# https://developers.home-assistant.io/docs/core/entity/fan/
|
||||
#
|
||||
def turn_on(
|
||||
self,
|
||||
speed: Optional[str] = None,
|
||||
percentage: Optional[int] = None,
|
||||
preset_mode: Optional[str] = None,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
"""Turn on the fan."""
|
||||
_LOGGER.debug("Turn on fan %s with speed %s", self.name, speed)
|
||||
if speed is not None:
|
||||
|
@ -299,7 +312,20 @@ class DysonPureCoolEntity(DysonFanEntity):
|
|||
"""Initialize the fan."""
|
||||
super().__init__(device, DysonPureCoolV2State)
|
||||
|
||||
def turn_on(self, speed: Optional[str] = None, **kwargs) -> None:
|
||||
#
|
||||
# The fan entity model has changed to use percentages and preset_modes
|
||||
# instead of speeds.
|
||||
#
|
||||
# Please review
|
||||
# https://developers.home-assistant.io/docs/core/entity/fan/
|
||||
#
|
||||
def turn_on(
|
||||
self,
|
||||
speed: Optional[str] = None,
|
||||
percentage: Optional[int] = None,
|
||||
preset_mode: Optional[str] = None,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
"""Turn on the fan."""
|
||||
_LOGGER.debug("Turn on fan %s", self.name)
|
||||
|
||||
|
|
|
@ -79,7 +79,20 @@ class EsphomeFan(EsphomeEntity, FanEntity):
|
|||
self._static_info.key, speed=_fan_speeds.from_hass(speed)
|
||||
)
|
||||
|
||||
async def async_turn_on(self, speed: Optional[str] = None, **kwargs) -> None:
|
||||
#
|
||||
# The fan entity model has changed to use percentages and preset_modes
|
||||
# instead of speeds.
|
||||
#
|
||||
# Please review
|
||||
# https://developers.home-assistant.io/docs/core/entity/fan/
|
||||
#
|
||||
async def async_turn_on(
|
||||
self,
|
||||
speed: Optional[str] = None,
|
||||
percentage: Optional[int] = None,
|
||||
preset_mode: Optional[str] = None,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
"""Turn on the fan."""
|
||||
if speed == SPEED_OFF:
|
||||
await self.async_turn_off()
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
from datetime import timedelta
|
||||
import functools as ft
|
||||
import logging
|
||||
from typing import Optional
|
||||
from typing import List, Optional
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
|
@ -20,6 +20,10 @@ from homeassistant.helpers.config_validation import ( # noqa: F401
|
|||
from homeassistant.helpers.entity import ToggleEntity
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.loader import bind_hass
|
||||
from homeassistant.util.percentage import (
|
||||
ordered_list_item_to_percentage,
|
||||
percentage_to_ordered_list_item,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -32,10 +36,13 @@ ENTITY_ID_FORMAT = DOMAIN + ".{}"
|
|||
SUPPORT_SET_SPEED = 1
|
||||
SUPPORT_OSCILLATE = 2
|
||||
SUPPORT_DIRECTION = 4
|
||||
SUPPORT_PRESET_MODE = 8
|
||||
|
||||
SERVICE_SET_SPEED = "set_speed"
|
||||
SERVICE_OSCILLATE = "oscillate"
|
||||
SERVICE_SET_DIRECTION = "set_direction"
|
||||
SERVICE_SET_PERCENTAGE = "set_percentage"
|
||||
SERVICE_SET_PRESET_MODE = "set_preset_mode"
|
||||
|
||||
SPEED_OFF = "off"
|
||||
SPEED_LOW = "low"
|
||||
|
@ -46,9 +53,47 @@ DIRECTION_FORWARD = "forward"
|
|||
DIRECTION_REVERSE = "reverse"
|
||||
|
||||
ATTR_SPEED = "speed"
|
||||
ATTR_PERCENTAGE = "percentage"
|
||||
ATTR_SPEED_LIST = "speed_list"
|
||||
ATTR_OSCILLATING = "oscillating"
|
||||
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_AUTO = "auto"
|
||||
_NOT_SPEED_SMART = "smart"
|
||||
_NOT_SPEED_INTERVAL = "interval"
|
||||
_NOT_SPEED_IDLE = "idle"
|
||||
_NOT_SPEED_FAVORITE = "favorite"
|
||||
|
||||
_NOT_SPEEDS_FILTER = {
|
||||
_NOT_SPEED_OFF,
|
||||
_NOT_SPEED_AUTO,
|
||||
_NOT_SPEED_SMART,
|
||||
_NOT_SPEED_INTERVAL,
|
||||
_NOT_SPEED_IDLE,
|
||||
_NOT_SPEED_FAVORITE,
|
||||
}
|
||||
|
||||
_FAN_NATIVE = "_fan_native"
|
||||
|
||||
OFF_SPEED_VALUES = [SPEED_OFF, None]
|
||||
|
||||
|
||||
class NoValidSpeedsError(ValueError):
|
||||
"""Exception class when there are no valid speeds."""
|
||||
|
||||
|
||||
class NotValidSpeedError(ValueError):
|
||||
"""Exception class when the speed in not in the speed list."""
|
||||
|
||||
|
||||
class NotValidPresetModeError(ValueError):
|
||||
"""Exception class when the preset_mode in not in the preset_modes list."""
|
||||
|
||||
|
||||
@bind_hass
|
||||
|
@ -56,7 +101,7 @@ def is_on(hass, entity_id: str) -> bool:
|
|||
"""Return if the fans are on based on the statemachine."""
|
||||
state = hass.states.get(entity_id)
|
||||
if ATTR_SPEED in state.attributes:
|
||||
return state.attributes[ATTR_SPEED] not in [SPEED_OFF, None]
|
||||
return state.attributes[ATTR_SPEED] not in OFF_SPEED_VALUES
|
||||
return state.state == STATE_ON
|
||||
|
||||
|
||||
|
@ -68,15 +113,27 @@ async def async_setup(hass, config: dict):
|
|||
|
||||
await component.async_setup(config)
|
||||
|
||||
# After the transition to percentage and preset_modes concludes,
|
||||
# switch this back to async_turn_on and remove async_turn_on_compat
|
||||
component.async_register_entity_service(
|
||||
SERVICE_TURN_ON, {vol.Optional(ATTR_SPEED): cv.string}, "async_turn_on"
|
||||
SERVICE_TURN_ON,
|
||||
{
|
||||
vol.Optional(ATTR_SPEED): cv.string,
|
||||
vol.Optional(ATTR_PERCENTAGE): vol.All(
|
||||
vol.Coerce(int), vol.Range(min=0, max=100)
|
||||
),
|
||||
vol.Optional(ATTR_PRESET_MODE): cv.string,
|
||||
},
|
||||
"async_turn_on_compat",
|
||||
)
|
||||
component.async_register_entity_service(SERVICE_TURN_OFF, {}, "async_turn_off")
|
||||
component.async_register_entity_service(SERVICE_TOGGLE, {}, "async_toggle")
|
||||
# After the transition to percentage and preset_modes concludes,
|
||||
# remove this service
|
||||
component.async_register_entity_service(
|
||||
SERVICE_SET_SPEED,
|
||||
{vol.Required(ATTR_SPEED): cv.string},
|
||||
"async_set_speed",
|
||||
"async_set_speed_deprecated",
|
||||
[SUPPORT_SET_SPEED],
|
||||
)
|
||||
component.async_register_entity_service(
|
||||
|
@ -91,6 +148,22 @@ async def async_setup(hass, config: dict):
|
|||
"async_set_direction",
|
||||
[SUPPORT_DIRECTION],
|
||||
)
|
||||
component.async_register_entity_service(
|
||||
SERVICE_SET_PERCENTAGE,
|
||||
{
|
||||
vol.Required(ATTR_PERCENTAGE): vol.All(
|
||||
vol.Coerce(int), vol.Range(min=0, max=100)
|
||||
)
|
||||
},
|
||||
"async_set_percentage",
|
||||
[SUPPORT_SET_SPEED],
|
||||
)
|
||||
component.async_register_entity_service(
|
||||
SERVICE_SET_PRESET_MODE,
|
||||
{vol.Required(ATTR_PRESET_MODE): cv.string},
|
||||
"async_set_preset_mode",
|
||||
[SUPPORT_SET_SPEED, SUPPORT_PRESET_MODE],
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
@ -105,19 +178,91 @@ async def async_unload_entry(hass, entry):
|
|||
return await hass.data[DOMAIN].async_unload_entry(entry)
|
||||
|
||||
|
||||
def _fan_native(method):
|
||||
"""Native fan method not overridden."""
|
||||
setattr(method, _FAN_NATIVE, True)
|
||||
return method
|
||||
|
||||
|
||||
class FanEntity(ToggleEntity):
|
||||
"""Representation of a fan."""
|
||||
|
||||
@_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(
|
||||
"fan.set_speed is deprecated, 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:
|
||||
await self.async_turn_off()
|
||||
return
|
||||
|
||||
if 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:
|
||||
await self.hass.async_add_executor_job(self.set_speed, speed)
|
||||
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.hass.async_add_executor_job(self.set_speed, 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))
|
||||
|
||||
@_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)
|
||||
|
||||
@_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)
|
||||
|
||||
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:
|
||||
raise NotValidPresetModeError(
|
||||
f"The preset_mode {preset_mode} is not a valid preset_mode: {preset_modes}"
|
||||
)
|
||||
|
||||
def set_direction(self, direction: str) -> None:
|
||||
"""Set the direction of the fan."""
|
||||
|
@ -128,18 +273,75 @@ class FanEntity(ToggleEntity):
|
|||
await self.hass.async_add_executor_job(self.set_direction, direction)
|
||||
|
||||
# pylint: disable=arguments-differ
|
||||
def turn_on(self, speed: Optional[str] = None, **kwargs) -> None:
|
||||
def turn_on(
|
||||
self,
|
||||
speed: Optional[str] = None,
|
||||
percentage: Optional[int] = None,
|
||||
preset_mode: Optional[str] = None,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
"""Turn on the fan."""
|
||||
raise NotImplementedError()
|
||||
|
||||
# pylint: disable=arguments-differ
|
||||
async def async_turn_on(self, speed: Optional[str] = None, **kwargs):
|
||||
async def async_turn_on_compat(
|
||||
self,
|
||||
speed: Optional[str] = None,
|
||||
percentage: Optional[int] = None,
|
||||
preset_mode: Optional[str] = None,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
"""Turn on the fan.
|
||||
|
||||
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.
|
||||
"""
|
||||
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."
|
||||
)
|
||||
if speed in self.preset_modes:
|
||||
preset_mode = speed
|
||||
percentage = None
|
||||
else:
|
||||
percentage = self.speed_to_percentage(speed)
|
||||
elif percentage is not None:
|
||||
speed = self.percentage_to_speed(percentage)
|
||||
|
||||
await self.async_turn_on(
|
||||
speed=speed,
|
||||
percentage=percentage,
|
||||
preset_mode=preset_mode,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
# pylint: disable=arguments-differ
|
||||
async def async_turn_on(
|
||||
self,
|
||||
speed: Optional[str] = None,
|
||||
percentage: Optional[int] = None,
|
||||
preset_mode: Optional[str] = None,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
"""Turn on the fan."""
|
||||
if speed == SPEED_OFF:
|
||||
await self.async_turn_off()
|
||||
else:
|
||||
await self.hass.async_add_executor_job(
|
||||
ft.partial(self.turn_on, speed, **kwargs)
|
||||
ft.partial(
|
||||
self.turn_on,
|
||||
speed=speed,
|
||||
percentage=percentage,
|
||||
preset_mode=preset_mode,
|
||||
**kwargs,
|
||||
)
|
||||
)
|
||||
|
||||
def oscillate(self, oscillating: bool) -> None:
|
||||
|
@ -155,15 +357,57 @@ class FanEntity(ToggleEntity):
|
|||
"""Return true if the entity is on."""
|
||||
return self.speed not in [SPEED_OFF, None]
|
||||
|
||||
@property
|
||||
def _implemented_percentage(self):
|
||||
"""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):
|
||||
"""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):
|
||||
"""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) -> Optional[str]:
|
||||
"""Return the current speed."""
|
||||
if self._implemented_preset_mode:
|
||||
preset_mode = self.preset_mode
|
||||
if preset_mode:
|
||||
return preset_mode
|
||||
if self._implemented_percentage:
|
||||
return self.percentage_to_speed(self.percentage)
|
||||
return None
|
||||
|
||||
@property
|
||||
def percentage(self) -> Optional[int]:
|
||||
"""Return the current speed as a percentage."""
|
||||
if not self._implemented_preset_mode:
|
||||
if self.speed in self.preset_modes:
|
||||
return None
|
||||
if not self._implemented_percentage:
|
||||
return self.speed_to_percentage(self.speed)
|
||||
return 0
|
||||
|
||||
@property
|
||||
def speed_list(self) -> list:
|
||||
"""Get the list of available speeds."""
|
||||
return []
|
||||
speeds = []
|
||||
if self._implemented_percentage:
|
||||
speeds += [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
|
||||
if self._implemented_preset_mode:
|
||||
speeds += self.preset_modes
|
||||
return speeds
|
||||
|
||||
@property
|
||||
def current_direction(self) -> Optional[str]:
|
||||
|
@ -178,9 +422,79 @@ class FanEntity(ToggleEntity):
|
|||
@property
|
||||
def capability_attributes(self):
|
||||
"""Return capability attributes."""
|
||||
attrs = {}
|
||||
if self.supported_features & SUPPORT_SET_SPEED:
|
||||
return {ATTR_SPEED_LIST: self.speed_list}
|
||||
return {}
|
||||
attrs[ATTR_SPEED_LIST] = self.speed_list
|
||||
|
||||
if (
|
||||
self.supported_features & SUPPORT_SET_SPEED
|
||||
or self.supported_features & SUPPORT_PRESET_MODE
|
||||
):
|
||||
attrs[ATTR_PRESET_MODES] = self.preset_modes
|
||||
|
||||
return attrs
|
||||
|
||||
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.
|
||||
"""
|
||||
if speed in OFF_SPEED_VALUES:
|
||||
return 0
|
||||
|
||||
speed_list = speed_list_without_preset_modes(self.speed_list)
|
||||
|
||||
if speed_list and speed not in speed_list:
|
||||
raise NotValidSpeedError(f"The speed {speed} is not a valid 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.
|
||||
"""
|
||||
if percentage == 0:
|
||||
return SPEED_OFF
|
||||
|
||||
speed_list = speed_list_without_preset_modes(self.speed_list)
|
||||
|
||||
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
|
||||
|
||||
@property
|
||||
def state_attributes(self) -> dict:
|
||||
|
@ -196,6 +510,13 @@ class FanEntity(ToggleEntity):
|
|||
|
||||
if supported_features & SUPPORT_SET_SPEED:
|
||||
data[ATTR_SPEED] = self.speed
|
||||
data[ATTR_PERCENTAGE] = self.percentage
|
||||
|
||||
if (
|
||||
supported_features & SUPPORT_PRESET_MODE
|
||||
or supported_features & SUPPORT_SET_SPEED
|
||||
):
|
||||
data[ATTR_PRESET_MODE] = self.preset_mode
|
||||
|
||||
return data
|
||||
|
||||
|
@ -203,3 +524,72 @@ class FanEntity(ToggleEntity):
|
|||
def supported_features(self) -> int:
|
||||
"""Flag supported features."""
|
||||
return 0
|
||||
|
||||
@property
|
||||
def preset_mode(self) -> Optional[str]:
|
||||
"""Return the current preset mode, e.g., auto, smart, interval, favorite.
|
||||
|
||||
Requires SUPPORT_SET_SPEED.
|
||||
"""
|
||||
speed = self.speed
|
||||
if speed in self.preset_modes:
|
||||
return speed
|
||||
return None
|
||||
|
||||
@property
|
||||
def preset_modes(self) -> Optional[List[str]]:
|
||||
"""Return a list of available preset modes.
|
||||
|
||||
Requires SUPPORT_SET_SPEED.
|
||||
"""
|
||||
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: ["Silent", "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", "Favorite", "Idle"]
|
||||
"""
|
||||
|
||||
return [
|
||||
speed
|
||||
for speed in speed_list
|
||||
if speed.lower() in _NOT_SPEEDS_FILTER and speed.lower() != SPEED_OFF
|
||||
]
|
||||
|
|
|
@ -17,10 +17,14 @@ from homeassistant.helpers.typing import HomeAssistantType
|
|||
from . import (
|
||||
ATTR_DIRECTION,
|
||||
ATTR_OSCILLATING,
|
||||
ATTR_PERCENTAGE,
|
||||
ATTR_PRESET_MODE,
|
||||
ATTR_SPEED,
|
||||
DOMAIN,
|
||||
SERVICE_OSCILLATE,
|
||||
SERVICE_SET_DIRECTION,
|
||||
SERVICE_SET_PERCENTAGE,
|
||||
SERVICE_SET_PRESET_MODE,
|
||||
SERVICE_SET_SPEED,
|
||||
)
|
||||
|
||||
|
@ -31,6 +35,8 @@ ATTRIBUTES = { # attribute: service
|
|||
ATTR_DIRECTION: SERVICE_SET_DIRECTION,
|
||||
ATTR_OSCILLATING: SERVICE_OSCILLATE,
|
||||
ATTR_SPEED: SERVICE_SET_SPEED,
|
||||
ATTR_PERCENTAGE: SERVICE_SET_PERCENTAGE,
|
||||
ATTR_PRESET_MODE: SERVICE_SET_PRESET_MODE,
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -9,6 +9,26 @@ set_speed:
|
|||
description: Speed setting
|
||||
example: "low"
|
||||
|
||||
set_preset_mode:
|
||||
description: Set preset mode for a fan device.
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name(s) of entities to change.
|
||||
example: "fan.kitchen"
|
||||
preset_mode:
|
||||
description: New value of preset mode
|
||||
example: "auto"
|
||||
|
||||
set_percentage:
|
||||
description: Sets fan speed percentage.
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name(s) of the entities to set
|
||||
example: "fan.living_room"
|
||||
percentage:
|
||||
description: Percentage speed setting
|
||||
example: 25
|
||||
|
||||
turn_on:
|
||||
description: Turns fan on.
|
||||
fields:
|
||||
|
@ -18,6 +38,12 @@ turn_on:
|
|||
speed:
|
||||
description: Speed setting
|
||||
example: "high"
|
||||
percentage:
|
||||
description: Percentage speed setting
|
||||
example: 75
|
||||
preset_mode:
|
||||
description: Preset mode setting
|
||||
example: "auto"
|
||||
|
||||
turn_off:
|
||||
description: Turns fan off.
|
||||
|
|
|
@ -130,9 +130,17 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity):
|
|||
{CharacteristicsTypes.SWING_MODE: 1 if oscillating else 0}
|
||||
)
|
||||
|
||||
async def async_turn_on(self, speed=None, **kwargs):
|
||||
#
|
||||
# The fan entity model has changed to use percentages and preset_modes
|
||||
# instead of speeds.
|
||||
#
|
||||
# Please review
|
||||
# https://developers.home-assistant.io/docs/core/entity/fan/
|
||||
#
|
||||
async def async_turn_on(
|
||||
self, speed=None, percentage=None, preset_mode=None, **kwargs
|
||||
):
|
||||
"""Turn the specified fan on."""
|
||||
|
||||
characteristics = {}
|
||||
|
||||
if not self.is_on:
|
||||
|
|
|
@ -63,7 +63,20 @@ class InsteonFanEntity(InsteonEntity, FanEntity):
|
|||
"""Flag supported features."""
|
||||
return SUPPORT_SET_SPEED
|
||||
|
||||
async def async_turn_on(self, speed: str = None, **kwargs) -> None:
|
||||
#
|
||||
# The fan entity model has changed to use percentages and preset_modes
|
||||
# instead of speeds.
|
||||
#
|
||||
# Please review
|
||||
# https://developers.home-assistant.io/docs/core/entity/fan/
|
||||
#
|
||||
async def async_turn_on(
|
||||
self,
|
||||
speed: str = None,
|
||||
percentage: int = None,
|
||||
preset_mode: str = None,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
"""Turn on the fan."""
|
||||
if speed is None:
|
||||
speed = SPEED_MEDIUM
|
||||
|
|
|
@ -71,7 +71,20 @@ class ISYFanEntity(ISYNodeEntity, FanEntity):
|
|||
"""Send the set speed command to the ISY994 fan device."""
|
||||
self._node.turn_on(val=STATE_TO_VALUE.get(speed, 255))
|
||||
|
||||
def turn_on(self, speed: str = None, **kwargs) -> None:
|
||||
#
|
||||
# The fan entity model has changed to use percentages and preset_modes
|
||||
# instead of speeds.
|
||||
#
|
||||
# Please review
|
||||
# https://developers.home-assistant.io/docs/core/entity/fan/
|
||||
#
|
||||
def turn_on(
|
||||
self,
|
||||
speed: str = None,
|
||||
percentage: int = None,
|
||||
preset_mode: str = None,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
"""Send the turn on command to the ISY994 fan device."""
|
||||
self.set_speed(speed)
|
||||
|
||||
|
@ -108,7 +121,20 @@ class ISYFanProgramEntity(ISYProgramEntity, FanEntity):
|
|||
if not self._actions.run_then():
|
||||
_LOGGER.error("Unable to turn off the fan")
|
||||
|
||||
def turn_on(self, speed: str = None, **kwargs) -> None:
|
||||
#
|
||||
# The fan entity model has changed to use percentages and preset_modes
|
||||
# instead of speeds.
|
||||
#
|
||||
# Please review
|
||||
# https://developers.home-assistant.io/docs/core/entity/fan/
|
||||
#
|
||||
def turn_on(
|
||||
self,
|
||||
speed: str = None,
|
||||
percentage: int = None,
|
||||
preset_mode: str = None,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
"""Send the turn off command to ISY994 fan program."""
|
||||
if not self._actions.run_else():
|
||||
_LOGGER.error("Unable to turn on the fan")
|
||||
|
|
|
@ -75,7 +75,20 @@ class LutronCasetaFan(LutronCasetaDevice, FanEntity):
|
|||
"""Flag supported features. Speed Only."""
|
||||
return SUPPORT_SET_SPEED
|
||||
|
||||
async def async_turn_on(self, speed: str = None, **kwargs):
|
||||
#
|
||||
# The fan entity model has changed to use percentages and preset_modes
|
||||
# instead of speeds.
|
||||
#
|
||||
# Please review
|
||||
# https://developers.home-assistant.io/docs/core/entity/fan/
|
||||
#
|
||||
async def async_turn_on(
|
||||
self,
|
||||
speed: str = None,
|
||||
percentage: int = None,
|
||||
preset_mode: str = None,
|
||||
**kwargs,
|
||||
):
|
||||
"""Turn the fan on."""
|
||||
if speed is None:
|
||||
speed = SPEED_MEDIUM
|
||||
|
|
|
@ -317,7 +317,20 @@ class MqttFan(MqttEntity, FanEntity):
|
|||
"""Return the oscillation state."""
|
||||
return self._oscillation
|
||||
|
||||
async def async_turn_on(self, speed: str = None, **kwargs) -> None:
|
||||
#
|
||||
# The fan entity model has changed to use percentages and preset_modes
|
||||
# instead of speeds.
|
||||
#
|
||||
# Please review
|
||||
# https://developers.home-assistant.io/docs/core/entity/fan/
|
||||
#
|
||||
async def async_turn_on(
|
||||
self,
|
||||
speed: str = None,
|
||||
percentage: int = None,
|
||||
preset_mode: str = None,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
"""Turn on the entity.
|
||||
|
||||
This method is a coroutine.
|
||||
|
|
|
@ -57,7 +57,16 @@ class ZwaveFan(ZWaveDeviceEntity, FanEntity):
|
|||
self._previous_speed = speed
|
||||
self.values.primary.send_value(SPEED_TO_VALUE[speed])
|
||||
|
||||
async def async_turn_on(self, speed=None, **kwargs):
|
||||
#
|
||||
# The fan entity model has changed to use percentages and preset_modes
|
||||
# instead of speeds.
|
||||
#
|
||||
# Please review
|
||||
# https://developers.home-assistant.io/docs/core/entity/fan/
|
||||
#
|
||||
async def async_turn_on(
|
||||
self, speed=None, percentage=None, preset_mode=None, **kwargs
|
||||
):
|
||||
"""Turn the device on."""
|
||||
if speed is None:
|
||||
# Value 255 tells device to return to previous value
|
||||
|
|
|
@ -50,7 +50,20 @@ class SmartThingsFan(SmartThingsEntity, FanEntity):
|
|||
# the entity state ahead of receiving the confirming push updates
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_turn_on(self, speed: str = None, **kwargs) -> None:
|
||||
#
|
||||
# The fan entity model has changed to use percentages and preset_modes
|
||||
# instead of speeds.
|
||||
#
|
||||
# Please review
|
||||
# https://developers.home-assistant.io/docs/core/entity/fan/
|
||||
#
|
||||
async def async_turn_on(
|
||||
self,
|
||||
speed: str = None,
|
||||
percentage: int = None,
|
||||
preset_mode: str = None,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
"""Turn the fan on."""
|
||||
if speed is not None:
|
||||
value = SPEED_TO_VALUE[speed]
|
||||
|
|
|
@ -86,7 +86,14 @@ class SmartyFan(FanEntity):
|
|||
self._speed = speed
|
||||
self._state = True
|
||||
|
||||
def turn_on(self, speed=None, **kwargs):
|
||||
#
|
||||
# The fan entity model has changed to use percentages and preset_modes
|
||||
# instead of speeds.
|
||||
#
|
||||
# Please review
|
||||
# https://developers.home-assistant.io/docs/core/entity/fan/
|
||||
#
|
||||
def turn_on(self, speed=None, percentage=None, preset_mode=None, **kwargs):
|
||||
"""Turn on the fan."""
|
||||
_LOGGER.debug("Turning on fan. Speed is %s", speed)
|
||||
if speed is None:
|
||||
|
|
|
@ -79,7 +79,16 @@ class TasmotaFan(
|
|||
else:
|
||||
self._tasmota_entity.set_speed(HA_TO_TASMOTA_SPEED_MAP[speed])
|
||||
|
||||
async def async_turn_on(self, speed=None, **kwargs):
|
||||
#
|
||||
# The fan entity model has changed to use percentages and preset_modes
|
||||
# instead of speeds.
|
||||
#
|
||||
# Please review
|
||||
# https://developers.home-assistant.io/docs/core/entity/fan/
|
||||
#
|
||||
async def async_turn_on(
|
||||
self, speed=None, percentage=None, preset_mode=None, **kwargs
|
||||
):
|
||||
"""Turn the fan on."""
|
||||
# Tasmota does not support turning a fan on with implicit speed
|
||||
await self.async_set_speed(speed or fan.SPEED_MEDIUM)
|
||||
|
|
|
@ -251,8 +251,20 @@ class TemplateFan(TemplateEntity, FanEntity):
|
|||
"""Return the oscillation state."""
|
||||
return self._direction
|
||||
|
||||
# pylint: disable=arguments-differ
|
||||
async def async_turn_on(self, speed: str = None) -> None:
|
||||
#
|
||||
# The fan entity model has changed to use percentages and preset_modes
|
||||
# instead of speeds.
|
||||
#
|
||||
# Please review
|
||||
# https://developers.home-assistant.io/docs/core/entity/fan/
|
||||
#
|
||||
async def async_turn_on(
|
||||
self,
|
||||
speed: str = None,
|
||||
percentage: int = None,
|
||||
preset_mode: str = None,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
"""Turn on the fan."""
|
||||
await self._on_script.async_run({ATTR_SPEED: speed}, context=self._context)
|
||||
self._state = STATE_ON
|
||||
|
|
|
@ -75,7 +75,20 @@ class TuyaFanDevice(TuyaDevice, FanEntity):
|
|||
else:
|
||||
self._tuya.set_speed(speed)
|
||||
|
||||
def turn_on(self, speed: str = None, **kwargs) -> None:
|
||||
#
|
||||
# The fan entity model has changed to use percentages and preset_modes
|
||||
# instead of speeds.
|
||||
#
|
||||
# Please review
|
||||
# https://developers.home-assistant.io/docs/core/entity/fan/
|
||||
#
|
||||
def turn_on(
|
||||
self,
|
||||
speed: str = None,
|
||||
percentage: int = None,
|
||||
preset_mode: str = None,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
"""Turn on the fan."""
|
||||
if speed is not None:
|
||||
self.set_speed(speed)
|
||||
|
|
|
@ -137,7 +137,20 @@ class ValloxFan(FanEntity):
|
|||
self._available = False
|
||||
_LOGGER.error("Error updating fan: %s", err)
|
||||
|
||||
async def async_turn_on(self, speed: str = None, **kwargs) -> None:
|
||||
#
|
||||
# The fan entity model has changed to use percentages and preset_modes
|
||||
# instead of speeds.
|
||||
#
|
||||
# Please review
|
||||
# https://developers.home-assistant.io/docs/core/entity/fan/
|
||||
#
|
||||
async def async_turn_on(
|
||||
self,
|
||||
speed: str = None,
|
||||
percentage: int = None,
|
||||
preset_mode: str = None,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
"""Turn the device on."""
|
||||
_LOGGER.debug("Turn on: %s", speed)
|
||||
|
||||
|
|
|
@ -107,7 +107,20 @@ class VeSyncFanHA(VeSyncDevice, FanEntity):
|
|||
self.smartfan.manual_mode()
|
||||
self.smartfan.change_fan_speed(FAN_SPEEDS.index(speed))
|
||||
|
||||
def turn_on(self, speed: str = None, **kwargs) -> None:
|
||||
#
|
||||
# The fan entity model has changed to use percentages and preset_modes
|
||||
# instead of speeds.
|
||||
#
|
||||
# Please review
|
||||
# https://developers.home-assistant.io/docs/core/entity/fan/
|
||||
#
|
||||
def turn_on(
|
||||
self,
|
||||
speed: str = None,
|
||||
percentage: int = None,
|
||||
preset_mode: str = None,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
"""Turn the device on."""
|
||||
self.smartfan.turn_on()
|
||||
self.set_speed(speed)
|
||||
|
|
|
@ -185,7 +185,20 @@ class WemoHumidifier(WemoSubscriptionEntity, FanEntity):
|
|||
self._available = False
|
||||
self.wemo.reconnect_with_device()
|
||||
|
||||
def turn_on(self, speed: str = None, **kwargs) -> None:
|
||||
#
|
||||
# The fan entity model has changed to use percentages and preset_modes
|
||||
# instead of speeds.
|
||||
#
|
||||
# Please review
|
||||
# https://developers.home-assistant.io/docs/core/entity/fan/
|
||||
#
|
||||
def turn_on(
|
||||
self,
|
||||
speed: str = None,
|
||||
percentage: int = None,
|
||||
preset_mode: str = None,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
"""Turn the switch on."""
|
||||
if speed is None:
|
||||
try:
|
||||
|
|
|
@ -94,7 +94,20 @@ class WiLightFan(WiLightDevice, FanEntity):
|
|||
self._direction = self._status["direction"]
|
||||
return self._direction
|
||||
|
||||
async def async_turn_on(self, speed: str = None, **kwargs):
|
||||
#
|
||||
# The fan entity model has changed to use percentages and preset_modes
|
||||
# instead of speeds.
|
||||
#
|
||||
# Please review
|
||||
# https://developers.home-assistant.io/docs/core/entity/fan/
|
||||
#
|
||||
async def async_turn_on(
|
||||
self,
|
||||
speed: str = None,
|
||||
percentage: int = None,
|
||||
preset_mode: str = None,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
"""Turn on the fan."""
|
||||
if speed is None:
|
||||
await self._client.set_fan_direction(self._index, self._direction)
|
||||
|
|
|
@ -40,7 +40,20 @@ class WinkFanDevice(WinkDevice, FanEntity):
|
|||
"""Set the speed of the fan."""
|
||||
self.wink.set_state(True, speed)
|
||||
|
||||
def turn_on(self, speed: str = None, **kwargs) -> None:
|
||||
#
|
||||
# The fan entity model has changed to use percentages and preset_modes
|
||||
# instead of speeds.
|
||||
#
|
||||
# Please review
|
||||
# https://developers.home-assistant.io/docs/core/entity/fan/
|
||||
#
|
||||
def turn_on(
|
||||
self,
|
||||
speed: str = None,
|
||||
percentage: int = None,
|
||||
preset_mode: str = None,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
"""Turn on the fan."""
|
||||
self.wink.set_state(True, speed)
|
||||
|
||||
|
|
|
@ -718,7 +718,20 @@ class XiaomiGenericDevice(FanEntity):
|
|||
|
||||
return False
|
||||
|
||||
async def async_turn_on(self, speed: str = None, **kwargs) -> None:
|
||||
#
|
||||
# The fan entity model has changed to use percentages and preset_modes
|
||||
# instead of speeds.
|
||||
#
|
||||
# Please review
|
||||
# https://developers.home-assistant.io/docs/core/entity/fan/
|
||||
#
|
||||
async def async_turn_on(
|
||||
self,
|
||||
speed: str = None,
|
||||
percentage: int = None,
|
||||
preset_mode: str = None,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
"""Turn the device on."""
|
||||
if speed:
|
||||
# If operation mode was set the device must not be turned on.
|
||||
|
|
|
@ -95,7 +95,16 @@ class BaseFan(FanEntity):
|
|||
"""Flag supported features."""
|
||||
return SUPPORT_SET_SPEED
|
||||
|
||||
async def async_turn_on(self, speed: str = None, **kwargs) -> None:
|
||||
#
|
||||
# The fan entity model has changed to use percentages and preset_modes
|
||||
# instead of speeds.
|
||||
#
|
||||
# Please review
|
||||
# https://developers.home-assistant.io/docs/core/entity/fan/
|
||||
#
|
||||
async def async_turn_on(
|
||||
self, speed=None, percentage=None, preset_mode=None, **kwargs
|
||||
) -> None:
|
||||
"""Turn the entity on."""
|
||||
if speed is None:
|
||||
speed = SPEED_MEDIUM
|
||||
|
|
|
@ -58,7 +58,14 @@ class ZwaveFan(ZWaveDeviceEntity, FanEntity):
|
|||
"""Set the speed of the fan."""
|
||||
self.node.set_dimmer(self.values.primary.value_id, SPEED_TO_VALUE[speed])
|
||||
|
||||
def turn_on(self, speed=None, **kwargs):
|
||||
#
|
||||
# The fan entity model has changed to use percentages and preset_modes
|
||||
# instead of speeds.
|
||||
#
|
||||
# Please review
|
||||
# https://developers.home-assistant.io/docs/core/entity/fan/
|
||||
#
|
||||
def turn_on(self, speed=None, percentage=None, preset_mode=None, **kwargs):
|
||||
"""Turn the device on."""
|
||||
if speed is None:
|
||||
# Value 255 tells device to return to previous value
|
||||
|
|
|
@ -72,7 +72,20 @@ class ZwaveFan(ZWaveBaseEntity, FanEntity):
|
|||
target_value = self.get_zwave_value("targetValue")
|
||||
await self.info.node.async_set_value(target_value, SPEED_TO_VALUE[speed])
|
||||
|
||||
async def async_turn_on(self, speed: Optional[str] = None, **kwargs: Any) -> None:
|
||||
#
|
||||
# The fan entity model has changed to use percentages and preset_modes
|
||||
# instead of speeds.
|
||||
#
|
||||
# Please review
|
||||
# https://developers.home-assistant.io/docs/core/entity/fan/
|
||||
#
|
||||
async def async_turn_on(
|
||||
self,
|
||||
speed: Optional[str] = None,
|
||||
percentage: Optional[int] = None,
|
||||
preset_mode: Optional[str] = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Turn the device on."""
|
||||
if speed is None:
|
||||
# Value 255 tells device to return to previous value
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
"""Percentage util functions."""
|
||||
|
||||
from typing import List, Tuple
|
||||
|
||||
|
||||
def ordered_list_item_to_percentage(ordered_list: List[str], item: str) -> int:
|
||||
"""Determine the percentage of an item in an ordered list.
|
||||
|
||||
When using this utility for fan speeds, do not include "off"
|
||||
|
||||
Given the list: ["low", "medium", "high", "very_high"], this
|
||||
function will return the following when when the item is passed
|
||||
in:
|
||||
|
||||
low: 25
|
||||
medium: 50
|
||||
high: 75
|
||||
very_high: 100
|
||||
|
||||
"""
|
||||
if item not in ordered_list:
|
||||
raise ValueError
|
||||
|
||||
list_len = len(ordered_list)
|
||||
list_position = ordered_list.index(item) + 1
|
||||
return (list_position * 100) // list_len
|
||||
|
||||
|
||||
def percentage_to_ordered_list_item(ordered_list: List[str], percentage: int) -> str:
|
||||
"""Find the item that most closely matches the percentage in an ordered list.
|
||||
|
||||
When using this utility for fan speeds, do not include "off"
|
||||
|
||||
Given the list: ["low", "medium", "high", "very_high"], this
|
||||
function will return the following when when the item is passed
|
||||
in:
|
||||
|
||||
1-25: low
|
||||
26-50: medium
|
||||
51-75: high
|
||||
76-100: very_high
|
||||
"""
|
||||
list_len = len(ordered_list)
|
||||
if not list_len:
|
||||
raise ValueError
|
||||
|
||||
for offset, speed in enumerate(ordered_list):
|
||||
list_position = offset + 1
|
||||
upper_bound = (list_position * 100) // list_len
|
||||
if percentage <= upper_bound:
|
||||
return speed
|
||||
|
||||
return ordered_list[-1]
|
||||
|
||||
|
||||
def ranged_value_to_percentage(
|
||||
low_high_range: Tuple[float, float], value: float
|
||||
) -> int:
|
||||
"""Given a range of low and high values convert a single value to a percentage.
|
||||
|
||||
When using this utility for fan speeds, do not include 0 if it is off
|
||||
|
||||
Given a low value of 1 and a high value of 255 this function
|
||||
will return:
|
||||
|
||||
(1,255), 255: 100
|
||||
(1,255), 127: 50
|
||||
(1,255), 10: 4
|
||||
"""
|
||||
return int((value * 100) // (low_high_range[1] - low_high_range[0] + 1))
|
||||
|
||||
|
||||
def percentage_to_ranged_value(
|
||||
low_high_range: Tuple[float, float], percentage: int
|
||||
) -> float:
|
||||
"""Given a range of low and high values convert a percentage to a single value.
|
||||
|
||||
When using this utility for fan speeds, do not include 0 if it is off
|
||||
|
||||
Given a low value of 1 and a high value of 255 this function
|
||||
will return:
|
||||
|
||||
(1,255), 100: 255
|
||||
(1,255), 50: 127.5
|
||||
(1,255), 4: 10.2
|
||||
"""
|
||||
return (low_high_range[1] - low_high_range[0] + 1) * percentage / 100
|
|
@ -2,6 +2,7 @@
|
|||
import pytest
|
||||
|
||||
from homeassistant.components import fan
|
||||
from homeassistant.components.demo.fan import PRESET_MODE_AUTO, PRESET_MODE_SMART
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
ENTITY_MATCH_ALL,
|
||||
|
@ -12,7 +13,15 @@ from homeassistant.const import (
|
|||
)
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
FAN_ENTITY_ID = "fan.living_room_fan"
|
||||
FULL_FAN_ENTITY_IDS = ["fan.living_room_fan", "fan.percentage_full_fan"]
|
||||
FANS_WITH_PRESET_MODE_ONLY = ["fan.preset_only_limited_fan"]
|
||||
LIMITED_AND_FULL_FAN_ENTITY_IDS = FULL_FAN_ENTITY_IDS + [
|
||||
"fan.ceiling_fan",
|
||||
"fan.percentage_limited_fan",
|
||||
]
|
||||
FANS_WITH_PRESET_MODES = FULL_FAN_ENTITY_IDS + [
|
||||
"fan.percentage_limited_fan",
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
|
@ -22,124 +31,338 @@ async def setup_comp(hass):
|
|||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
async def test_turn_on(hass):
|
||||
@pytest.mark.parametrize("fan_entity_id", LIMITED_AND_FULL_FAN_ENTITY_IDS)
|
||||
async def test_turn_on(hass, fan_entity_id):
|
||||
"""Test turning on the device."""
|
||||
state = hass.states.get(FAN_ENTITY_ID)
|
||||
state = hass.states.get(fan_entity_id)
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
await hass.services.async_call(
|
||||
fan.DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: FAN_ENTITY_ID}, blocking=True
|
||||
fan.DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: fan_entity_id}, blocking=True
|
||||
)
|
||||
state = hass.states.get(FAN_ENTITY_ID)
|
||||
state = hass.states.get(fan_entity_id)
|
||||
assert state.state == STATE_ON
|
||||
|
||||
|
||||
@pytest.mark.parametrize("fan_entity_id", FULL_FAN_ENTITY_IDS)
|
||||
async def test_turn_on_with_speed_and_percentage(hass, fan_entity_id):
|
||||
"""Test turning on the device."""
|
||||
state = hass.states.get(fan_entity_id)
|
||||
assert state.state == STATE_OFF
|
||||
await hass.services.async_call(
|
||||
fan.DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: fan_entity_id, fan.ATTR_SPEED: fan.SPEED_HIGH},
|
||||
blocking=True,
|
||||
)
|
||||
state = hass.states.get(fan_entity_id)
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_HIGH
|
||||
assert state.attributes[fan.ATTR_PERCENTAGE] == 100
|
||||
|
||||
await hass.services.async_call(
|
||||
fan.DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: FAN_ENTITY_ID, fan.ATTR_SPEED: fan.SPEED_HIGH},
|
||||
{ATTR_ENTITY_ID: fan_entity_id, fan.ATTR_PERCENTAGE: 100},
|
||||
blocking=True,
|
||||
)
|
||||
state = hass.states.get(FAN_ENTITY_ID)
|
||||
state = hass.states.get(fan_entity_id)
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_HIGH
|
||||
assert state.attributes[fan.ATTR_PERCENTAGE] == 100
|
||||
|
||||
|
||||
async def test_turn_off(hass):
|
||||
@pytest.mark.parametrize("fan_entity_id", FANS_WITH_PRESET_MODE_ONLY)
|
||||
async def test_turn_on_with_preset_mode_only(hass, fan_entity_id):
|
||||
"""Test turning on the device with a preset_mode and no speed setting."""
|
||||
state = hass.states.get(fan_entity_id)
|
||||
assert state.state == STATE_OFF
|
||||
await hass.services.async_call(
|
||||
fan.DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: fan_entity_id, fan.ATTR_PRESET_MODE: PRESET_MODE_AUTO},
|
||||
blocking=True,
|
||||
)
|
||||
state = hass.states.get(fan_entity_id)
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes[fan.ATTR_PRESET_MODE] == PRESET_MODE_AUTO
|
||||
assert state.attributes[fan.ATTR_PRESET_MODES] == [
|
||||
PRESET_MODE_AUTO,
|
||||
PRESET_MODE_SMART,
|
||||
]
|
||||
|
||||
await hass.services.async_call(
|
||||
fan.DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: fan_entity_id, fan.ATTR_PRESET_MODE: PRESET_MODE_SMART},
|
||||
blocking=True,
|
||||
)
|
||||
state = hass.states.get(fan_entity_id)
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes[fan.ATTR_PRESET_MODE] == PRESET_MODE_SMART
|
||||
|
||||
await hass.services.async_call(
|
||||
fan.DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: fan_entity_id}, blocking=True
|
||||
)
|
||||
state = hass.states.get(fan_entity_id)
|
||||
assert state.state == STATE_OFF
|
||||
assert state.attributes[fan.ATTR_PRESET_MODE] is None
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
await hass.services.async_call(
|
||||
fan.DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: fan_entity_id, fan.ATTR_PRESET_MODE: "invalid"},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(fan_entity_id)
|
||||
assert state.state == STATE_OFF
|
||||
assert state.attributes[fan.ATTR_PRESET_MODE] is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize("fan_entity_id", FANS_WITH_PRESET_MODES)
|
||||
async def test_turn_on_with_preset_mode_and_speed(hass, fan_entity_id):
|
||||
"""Test turning on the device with a preset_mode and speed."""
|
||||
state = hass.states.get(fan_entity_id)
|
||||
assert state.state == STATE_OFF
|
||||
await hass.services.async_call(
|
||||
fan.DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: fan_entity_id, fan.ATTR_PRESET_MODE: 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
|
||||
assert state.attributes[fan.ATTR_SPEED_LIST] == [
|
||||
fan.SPEED_OFF,
|
||||
fan.SPEED_LOW,
|
||||
fan.SPEED_MEDIUM,
|
||||
fan.SPEED_HIGH,
|
||||
PRESET_MODE_AUTO,
|
||||
PRESET_MODE_SMART,
|
||||
]
|
||||
assert state.attributes[fan.ATTR_PRESET_MODES] == [
|
||||
PRESET_MODE_AUTO,
|
||||
PRESET_MODE_SMART,
|
||||
]
|
||||
|
||||
await hass.services.async_call(
|
||||
fan.DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: fan_entity_id, fan.ATTR_PERCENTAGE: 100},
|
||||
blocking=True,
|
||||
)
|
||||
state = hass.states.get(fan_entity_id)
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_HIGH
|
||||
assert state.attributes[fan.ATTR_PERCENTAGE] == 100
|
||||
assert state.attributes[fan.ATTR_PRESET_MODE] is None
|
||||
|
||||
await hass.services.async_call(
|
||||
fan.DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: fan_entity_id, fan.ATTR_PRESET_MODE: PRESET_MODE_SMART},
|
||||
blocking=True,
|
||||
)
|
||||
state = hass.states.get(fan_entity_id)
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes[fan.ATTR_SPEED] == PRESET_MODE_SMART
|
||||
assert state.attributes[fan.ATTR_PERCENTAGE] is None
|
||||
assert state.attributes[fan.ATTR_PRESET_MODE] == PRESET_MODE_SMART
|
||||
|
||||
await hass.services.async_call(
|
||||
fan.DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: fan_entity_id}, blocking=True
|
||||
)
|
||||
state = hass.states.get(fan_entity_id)
|
||||
assert state.state == STATE_OFF
|
||||
assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_OFF
|
||||
assert state.attributes[fan.ATTR_PERCENTAGE] == 0
|
||||
assert state.attributes[fan.ATTR_PRESET_MODE] is None
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
await hass.services.async_call(
|
||||
fan.DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: fan_entity_id, fan.ATTR_PRESET_MODE: "invalid"},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(fan_entity_id)
|
||||
assert state.state == STATE_OFF
|
||||
assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_OFF
|
||||
assert state.attributes[fan.ATTR_PERCENTAGE] == 0
|
||||
assert state.attributes[fan.ATTR_PRESET_MODE] is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize("fan_entity_id", LIMITED_AND_FULL_FAN_ENTITY_IDS)
|
||||
async def test_turn_off(hass, fan_entity_id):
|
||||
"""Test turning off the device."""
|
||||
state = hass.states.get(FAN_ENTITY_ID)
|
||||
state = hass.states.get(fan_entity_id)
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
await hass.services.async_call(
|
||||
fan.DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: FAN_ENTITY_ID}, blocking=True
|
||||
fan.DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: fan_entity_id}, blocking=True
|
||||
)
|
||||
state = hass.states.get(FAN_ENTITY_ID)
|
||||
state = hass.states.get(fan_entity_id)
|
||||
assert state.state == STATE_ON
|
||||
|
||||
await hass.services.async_call(
|
||||
fan.DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: FAN_ENTITY_ID}, blocking=True
|
||||
fan.DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: fan_entity_id}, blocking=True
|
||||
)
|
||||
state = hass.states.get(FAN_ENTITY_ID)
|
||||
state = hass.states.get(fan_entity_id)
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
|
||||
async def test_turn_off_without_entity_id(hass):
|
||||
@pytest.mark.parametrize("fan_entity_id", LIMITED_AND_FULL_FAN_ENTITY_IDS)
|
||||
async def test_turn_off_without_entity_id(hass, fan_entity_id):
|
||||
"""Test turning off all fans."""
|
||||
state = hass.states.get(FAN_ENTITY_ID)
|
||||
state = hass.states.get(fan_entity_id)
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
await hass.services.async_call(
|
||||
fan.DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: FAN_ENTITY_ID}, blocking=True
|
||||
fan.DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: fan_entity_id}, blocking=True
|
||||
)
|
||||
state = hass.states.get(FAN_ENTITY_ID)
|
||||
state = hass.states.get(fan_entity_id)
|
||||
assert state.state == STATE_ON
|
||||
|
||||
await hass.services.async_call(
|
||||
fan.DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: ENTITY_MATCH_ALL}, blocking=True
|
||||
)
|
||||
state = hass.states.get(FAN_ENTITY_ID)
|
||||
state = hass.states.get(fan_entity_id)
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
|
||||
async def test_set_direction(hass):
|
||||
@pytest.mark.parametrize("fan_entity_id", FULL_FAN_ENTITY_IDS)
|
||||
async def test_set_direction(hass, fan_entity_id):
|
||||
"""Test setting the direction of the device."""
|
||||
state = hass.states.get(FAN_ENTITY_ID)
|
||||
state = hass.states.get(fan_entity_id)
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
await hass.services.async_call(
|
||||
fan.DOMAIN,
|
||||
fan.SERVICE_SET_DIRECTION,
|
||||
{ATTR_ENTITY_ID: FAN_ENTITY_ID, fan.ATTR_DIRECTION: fan.DIRECTION_REVERSE},
|
||||
{ATTR_ENTITY_ID: fan_entity_id, fan.ATTR_DIRECTION: fan.DIRECTION_REVERSE},
|
||||
blocking=True,
|
||||
)
|
||||
state = hass.states.get(FAN_ENTITY_ID)
|
||||
state = hass.states.get(fan_entity_id)
|
||||
assert state.attributes[fan.ATTR_DIRECTION] == fan.DIRECTION_REVERSE
|
||||
|
||||
|
||||
async def test_set_speed(hass):
|
||||
@pytest.mark.parametrize("fan_entity_id", 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)
|
||||
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: fan.SPEED_LOW},
|
||||
{ATTR_ENTITY_ID: fan_entity_id, fan.ATTR_SPEED: fan.SPEED_LOW},
|
||||
blocking=True,
|
||||
)
|
||||
state = hass.states.get(FAN_ENTITY_ID)
|
||||
state = hass.states.get(fan_entity_id)
|
||||
assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_LOW
|
||||
|
||||
|
||||
async def test_oscillate(hass):
|
||||
@pytest.mark.parametrize("fan_entity_id", FANS_WITH_PRESET_MODES)
|
||||
async def test_set_preset_mode(hass, fan_entity_id):
|
||||
"""Test setting the preset mode of the device."""
|
||||
state = hass.states.get(fan_entity_id)
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
await hass.services.async_call(
|
||||
fan.DOMAIN,
|
||||
fan.SERVICE_SET_PRESET_MODE,
|
||||
{ATTR_ENTITY_ID: fan_entity_id, fan.ATTR_PRESET_MODE: 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", LIMITED_AND_FULL_FAN_ENTITY_IDS)
|
||||
async def test_set_preset_mode_invalid(hass, fan_entity_id):
|
||||
"""Test setting a invalid preset mode for the device."""
|
||||
state = hass.states.get(fan_entity_id)
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
await hass.services.async_call(
|
||||
fan.DOMAIN,
|
||||
fan.SERVICE_SET_PRESET_MODE,
|
||||
{ATTR_ENTITY_ID: fan_entity_id, fan.ATTR_PRESET_MODE: "invalid"},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
await hass.services.async_call(
|
||||
fan.DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: fan_entity_id, fan.ATTR_PRESET_MODE: "invalid"},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("fan_entity_id", FULL_FAN_ENTITY_IDS)
|
||||
async def test_set_percentage(hass, fan_entity_id):
|
||||
"""Test setting the percentage speed of the device."""
|
||||
state = hass.states.get(fan_entity_id)
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
await hass.services.async_call(
|
||||
fan.DOMAIN,
|
||||
fan.SERVICE_SET_PERCENTAGE,
|
||||
{ATTR_ENTITY_ID: fan_entity_id, fan.ATTR_PERCENTAGE: 33},
|
||||
blocking=True,
|
||||
)
|
||||
state = hass.states.get(fan_entity_id)
|
||||
assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_LOW
|
||||
assert state.attributes[fan.ATTR_PERCENTAGE] == 33
|
||||
|
||||
|
||||
@pytest.mark.parametrize("fan_entity_id", FULL_FAN_ENTITY_IDS)
|
||||
async def test_oscillate(hass, fan_entity_id):
|
||||
"""Test oscillating the fan."""
|
||||
state = hass.states.get(FAN_ENTITY_ID)
|
||||
state = hass.states.get(fan_entity_id)
|
||||
assert state.state == STATE_OFF
|
||||
assert not state.attributes.get(fan.ATTR_OSCILLATING)
|
||||
|
||||
await hass.services.async_call(
|
||||
fan.DOMAIN,
|
||||
fan.SERVICE_OSCILLATE,
|
||||
{ATTR_ENTITY_ID: FAN_ENTITY_ID, fan.ATTR_OSCILLATING: True},
|
||||
{ATTR_ENTITY_ID: fan_entity_id, fan.ATTR_OSCILLATING: True},
|
||||
blocking=True,
|
||||
)
|
||||
state = hass.states.get(FAN_ENTITY_ID)
|
||||
state = hass.states.get(fan_entity_id)
|
||||
assert state.attributes[fan.ATTR_OSCILLATING] is True
|
||||
|
||||
await hass.services.async_call(
|
||||
fan.DOMAIN,
|
||||
fan.SERVICE_OSCILLATE,
|
||||
{ATTR_ENTITY_ID: FAN_ENTITY_ID, fan.ATTR_OSCILLATING: False},
|
||||
{ATTR_ENTITY_ID: fan_entity_id, fan.ATTR_OSCILLATING: False},
|
||||
blocking=True,
|
||||
)
|
||||
state = hass.states.get(FAN_ENTITY_ID)
|
||||
state = hass.states.get(fan_entity_id)
|
||||
assert state.attributes[fan.ATTR_OSCILLATING] is False
|
||||
|
||||
|
||||
async def test_is_on(hass):
|
||||
@pytest.mark.parametrize("fan_entity_id", LIMITED_AND_FULL_FAN_ENTITY_IDS)
|
||||
async def test_is_on(hass, fan_entity_id):
|
||||
"""Test is on service call."""
|
||||
assert not fan.is_on(hass, FAN_ENTITY_ID)
|
||||
assert not fan.is_on(hass, fan_entity_id)
|
||||
|
||||
await hass.services.async_call(
|
||||
fan.DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: FAN_ENTITY_ID}, blocking=True
|
||||
fan.DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: fan_entity_id}, blocking=True
|
||||
)
|
||||
assert fan.is_on(hass, FAN_ENTITY_ID)
|
||||
assert fan.is_on(hass, fan_entity_id)
|
||||
|
|
|
@ -70,16 +70,19 @@ ENTITY_IDS_BY_NUMBER = {
|
|||
"8": "media_player.lounge_room",
|
||||
"9": "fan.living_room_fan",
|
||||
"10": "fan.ceiling_fan",
|
||||
"11": "cover.living_room_window",
|
||||
"12": "climate.hvac",
|
||||
"13": "climate.heatpump",
|
||||
"14": "climate.ecobee",
|
||||
"15": "light.no_brightness",
|
||||
"16": "humidifier.humidifier",
|
||||
"17": "humidifier.dehumidifier",
|
||||
"18": "humidifier.hygrostat",
|
||||
"19": "scene.light_on",
|
||||
"20": "scene.light_off",
|
||||
"11": "fan.percentage_full_fan",
|
||||
"12": "fan.percentage_limited_fan",
|
||||
"13": "fan.preset_only_limited_fan",
|
||||
"14": "cover.living_room_window",
|
||||
"15": "climate.hvac",
|
||||
"16": "climate.heatpump",
|
||||
"17": "climate.ecobee",
|
||||
"18": "light.no_brightness",
|
||||
"19": "humidifier.humidifier",
|
||||
"20": "humidifier.dehumidifier",
|
||||
"21": "humidifier.hygrostat",
|
||||
"22": "scene.light_on",
|
||||
"23": "scene.light_off",
|
||||
}
|
||||
|
||||
ENTITY_NUMBERS_BY_ID = {v: k for k, v in ENTITY_IDS_BY_NUMBER.items()}
|
||||
|
|
|
@ -6,10 +6,14 @@ components. Instead call the service directly.
|
|||
from homeassistant.components.fan import (
|
||||
ATTR_DIRECTION,
|
||||
ATTR_OSCILLATING,
|
||||
ATTR_PERCENTAGE,
|
||||
ATTR_PRESET_MODE,
|
||||
ATTR_SPEED,
|
||||
DOMAIN,
|
||||
SERVICE_OSCILLATE,
|
||||
SERVICE_SET_DIRECTION,
|
||||
SERVICE_SET_PERCENTAGE,
|
||||
SERVICE_SET_PRESET_MODE,
|
||||
SERVICE_SET_SPEED,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
|
@ -20,11 +24,22 @@ from homeassistant.const import (
|
|||
)
|
||||
|
||||
|
||||
async def async_turn_on(hass, entity_id=ENTITY_MATCH_ALL, speed: str = None) -> None:
|
||||
async def async_turn_on(
|
||||
hass,
|
||||
entity_id=ENTITY_MATCH_ALL,
|
||||
speed: str = None,
|
||||
percentage: int = None,
|
||||
preset_mode: str = None,
|
||||
) -> None:
|
||||
"""Turn all or specified fan on."""
|
||||
data = {
|
||||
key: value
|
||||
for key, value in [(ATTR_ENTITY_ID, entity_id), (ATTR_SPEED, speed)]
|
||||
for key, value in [
|
||||
(ATTR_ENTITY_ID, entity_id),
|
||||
(ATTR_SPEED, speed),
|
||||
(ATTR_PERCENTAGE, percentage),
|
||||
(ATTR_PRESET_MODE, preset_mode),
|
||||
]
|
||||
if value is not None
|
||||
}
|
||||
|
||||
|
@ -65,6 +80,32 @@ async def async_set_speed(hass, entity_id=ENTITY_MATCH_ALL, speed: str = None) -
|
|||
await hass.services.async_call(DOMAIN, SERVICE_SET_SPEED, data, blocking=True)
|
||||
|
||||
|
||||
async def async_set_preset_mode(
|
||||
hass, entity_id=ENTITY_MATCH_ALL, preset_mode: str = None
|
||||
) -> None:
|
||||
"""Set preset mode for all or specified fan."""
|
||||
data = {
|
||||
key: value
|
||||
for key, value in [(ATTR_ENTITY_ID, entity_id), (ATTR_PRESET_MODE, preset_mode)]
|
||||
if value is not None
|
||||
}
|
||||
|
||||
await hass.services.async_call(DOMAIN, SERVICE_SET_PRESET_MODE, data, blocking=True)
|
||||
|
||||
|
||||
async def async_set_percentage(
|
||||
hass, entity_id=ENTITY_MATCH_ALL, percentage: int = None
|
||||
) -> None:
|
||||
"""Set percentage for all or specified fan."""
|
||||
data = {
|
||||
key: value
|
||||
for key, value in [(ATTR_ENTITY_ID, entity_id), (ATTR_PERCENTAGE, percentage)]
|
||||
if value is not None
|
||||
}
|
||||
|
||||
await hass.services.async_call(DOMAIN, SERVICE_SET_PERCENTAGE, data, blocking=True)
|
||||
|
||||
|
||||
async def async_set_direction(
|
||||
hass, entity_id=ENTITY_MATCH_ALL, direction: str = None
|
||||
) -> None:
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.fan import FanEntity
|
||||
from homeassistant.components.fan import FanEntity, NotValidPresetModeError
|
||||
|
||||
|
||||
class BaseFan(FanEntity):
|
||||
|
@ -17,6 +17,7 @@ def test_fanentity():
|
|||
fan = BaseFan()
|
||||
assert fan.state == "off"
|
||||
assert len(fan.speed_list) == 0
|
||||
assert len(fan.preset_modes) == 0
|
||||
assert fan.supported_features == 0
|
||||
assert fan.capability_attributes == {}
|
||||
# Test set_speed not required
|
||||
|
@ -24,7 +25,35 @@ def test_fanentity():
|
|||
fan.oscillate(True)
|
||||
with pytest.raises(NotImplementedError):
|
||||
fan.set_speed("slow")
|
||||
with pytest.raises(NotImplementedError):
|
||||
fan.set_percentage(0)
|
||||
with pytest.raises(NotValidPresetModeError):
|
||||
fan.set_preset_mode("auto")
|
||||
with pytest.raises(NotImplementedError):
|
||||
fan.turn_on()
|
||||
with pytest.raises(NotImplementedError):
|
||||
fan.turn_off()
|
||||
|
||||
|
||||
async def test_async_fanentity(hass):
|
||||
"""Test async fan entity methods."""
|
||||
fan = BaseFan()
|
||||
fan.hass = hass
|
||||
assert fan.state == "off"
|
||||
assert len(fan.speed_list) == 0
|
||||
assert len(fan.preset_modes) == 0
|
||||
assert fan.supported_features == 0
|
||||
assert fan.capability_attributes == {}
|
||||
# Test set_speed not required
|
||||
with pytest.raises(NotImplementedError):
|
||||
await fan.async_oscillate(True)
|
||||
with pytest.raises(NotImplementedError):
|
||||
await fan.async_set_speed("slow")
|
||||
with pytest.raises(NotImplementedError):
|
||||
await fan.async_set_percentage(0)
|
||||
with pytest.raises(NotValidPresetModeError):
|
||||
await fan.async_set_preset_mode("auto")
|
||||
with pytest.raises(NotImplementedError):
|
||||
await fan.async_turn_on()
|
||||
with pytest.raises(NotImplementedError):
|
||||
await fan.async_turn_off()
|
||||
|
|
|
@ -245,6 +245,27 @@ DEMO_DEVICES = [
|
|||
"type": "action.devices.types.FAN",
|
||||
"willReportState": False,
|
||||
},
|
||||
{
|
||||
"id": "fan.percentage_full_fan",
|
||||
"name": {"name": "Percentage Full Fan"},
|
||||
"traits": ["action.devices.traits.FanSpeed", "action.devices.traits.OnOff"],
|
||||
"type": "action.devices.types.FAN",
|
||||
"willReportState": False,
|
||||
},
|
||||
{
|
||||
"id": "fan.percentage_limited_fan",
|
||||
"name": {"name": "Percentage Limited Fan"},
|
||||
"traits": ["action.devices.traits.FanSpeed", "action.devices.traits.OnOff"],
|
||||
"type": "action.devices.types.FAN",
|
||||
"willReportState": False,
|
||||
},
|
||||
{
|
||||
"id": "fan.preset_only_limited_fan",
|
||||
"name": {"name": "Preset Only Limited Fan"},
|
||||
"traits": ["action.devices.traits.OnOff"],
|
||||
"type": "action.devices.types.FAN",
|
||||
"willReportState": False,
|
||||
},
|
||||
{
|
||||
"id": "climate.hvac",
|
||||
"name": {"name": "Hvac"},
|
||||
|
|
|
@ -0,0 +1,158 @@
|
|||
"""Test Home Assistant percentage conversions."""
|
||||
|
||||
import math
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.util.percentage import (
|
||||
ordered_list_item_to_percentage,
|
||||
percentage_to_ordered_list_item,
|
||||
percentage_to_ranged_value,
|
||||
ranged_value_to_percentage,
|
||||
)
|
||||
|
||||
SPEED_LOW = "low"
|
||||
SPEED_MEDIUM = "medium"
|
||||
SPEED_HIGH = "high"
|
||||
|
||||
SPEED_1 = SPEED_LOW
|
||||
SPEED_2 = SPEED_MEDIUM
|
||||
SPEED_3 = SPEED_HIGH
|
||||
SPEED_4 = "very_high"
|
||||
SPEED_5 = "storm"
|
||||
SPEED_6 = "hurricane"
|
||||
SPEED_7 = "solar_wind"
|
||||
|
||||
LEGACY_ORDERED_LIST = [SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
|
||||
SMALL_ORDERED_LIST = [SPEED_1, SPEED_2, SPEED_3, SPEED_4]
|
||||
LARGE_ORDERED_LIST = [SPEED_1, SPEED_2, SPEED_3, SPEED_4, SPEED_5, SPEED_6, SPEED_7]
|
||||
|
||||
|
||||
async def test_ordered_list_percentage_round_trip():
|
||||
"""Test we can round trip."""
|
||||
for ordered_list in (SMALL_ORDERED_LIST, LARGE_ORDERED_LIST):
|
||||
for i in range(1, 100):
|
||||
ordered_list_item_to_percentage(
|
||||
ordered_list, percentage_to_ordered_list_item(ordered_list, i)
|
||||
) == i
|
||||
|
||||
|
||||
async def test_ordered_list_item_to_percentage():
|
||||
"""Test percentage of an item in an ordered list."""
|
||||
|
||||
assert ordered_list_item_to_percentage(LEGACY_ORDERED_LIST, SPEED_LOW) == 33
|
||||
assert ordered_list_item_to_percentage(LEGACY_ORDERED_LIST, SPEED_MEDIUM) == 66
|
||||
assert ordered_list_item_to_percentage(LEGACY_ORDERED_LIST, SPEED_HIGH) == 100
|
||||
|
||||
assert ordered_list_item_to_percentage(SMALL_ORDERED_LIST, SPEED_1) == 25
|
||||
assert ordered_list_item_to_percentage(SMALL_ORDERED_LIST, SPEED_2) == 50
|
||||
assert ordered_list_item_to_percentage(SMALL_ORDERED_LIST, SPEED_3) == 75
|
||||
assert ordered_list_item_to_percentage(SMALL_ORDERED_LIST, SPEED_4) == 100
|
||||
|
||||
assert ordered_list_item_to_percentage(LARGE_ORDERED_LIST, SPEED_1) == 14
|
||||
assert ordered_list_item_to_percentage(LARGE_ORDERED_LIST, SPEED_2) == 28
|
||||
assert ordered_list_item_to_percentage(LARGE_ORDERED_LIST, SPEED_3) == 42
|
||||
assert ordered_list_item_to_percentage(LARGE_ORDERED_LIST, SPEED_4) == 57
|
||||
assert ordered_list_item_to_percentage(LARGE_ORDERED_LIST, SPEED_5) == 71
|
||||
assert ordered_list_item_to_percentage(LARGE_ORDERED_LIST, SPEED_6) == 85
|
||||
assert ordered_list_item_to_percentage(LARGE_ORDERED_LIST, SPEED_7) == 100
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
assert ordered_list_item_to_percentage([], SPEED_1)
|
||||
|
||||
|
||||
async def test_percentage_to_ordered_list_item():
|
||||
"""Test item that most closely matches the percentage in an ordered list."""
|
||||
|
||||
assert percentage_to_ordered_list_item(SMALL_ORDERED_LIST, 1) == SPEED_1
|
||||
assert percentage_to_ordered_list_item(SMALL_ORDERED_LIST, 25) == SPEED_1
|
||||
assert percentage_to_ordered_list_item(SMALL_ORDERED_LIST, 26) == SPEED_2
|
||||
assert percentage_to_ordered_list_item(SMALL_ORDERED_LIST, 50) == SPEED_2
|
||||
assert percentage_to_ordered_list_item(SMALL_ORDERED_LIST, 51) == SPEED_3
|
||||
assert percentage_to_ordered_list_item(SMALL_ORDERED_LIST, 75) == SPEED_3
|
||||
assert percentage_to_ordered_list_item(SMALL_ORDERED_LIST, 76) == SPEED_4
|
||||
assert percentage_to_ordered_list_item(SMALL_ORDERED_LIST, 100) == SPEED_4
|
||||
|
||||
assert percentage_to_ordered_list_item(LEGACY_ORDERED_LIST, 17) == SPEED_LOW
|
||||
assert percentage_to_ordered_list_item(LEGACY_ORDERED_LIST, 33) == SPEED_LOW
|
||||
assert percentage_to_ordered_list_item(LEGACY_ORDERED_LIST, 50) == SPEED_MEDIUM
|
||||
assert percentage_to_ordered_list_item(LEGACY_ORDERED_LIST, 66) == SPEED_MEDIUM
|
||||
assert percentage_to_ordered_list_item(LEGACY_ORDERED_LIST, 84) == SPEED_HIGH
|
||||
assert percentage_to_ordered_list_item(LEGACY_ORDERED_LIST, 100) == SPEED_HIGH
|
||||
|
||||
assert percentage_to_ordered_list_item(LARGE_ORDERED_LIST, 1) == SPEED_1
|
||||
assert percentage_to_ordered_list_item(LARGE_ORDERED_LIST, 14) == SPEED_1
|
||||
assert percentage_to_ordered_list_item(LARGE_ORDERED_LIST, 25) == SPEED_2
|
||||
assert percentage_to_ordered_list_item(LARGE_ORDERED_LIST, 26) == SPEED_2
|
||||
assert percentage_to_ordered_list_item(LARGE_ORDERED_LIST, 28) == SPEED_2
|
||||
assert percentage_to_ordered_list_item(LARGE_ORDERED_LIST, 29) == SPEED_3
|
||||
assert percentage_to_ordered_list_item(LARGE_ORDERED_LIST, 41) == SPEED_3
|
||||
assert percentage_to_ordered_list_item(LARGE_ORDERED_LIST, 42) == SPEED_3
|
||||
assert percentage_to_ordered_list_item(LARGE_ORDERED_LIST, 43) == SPEED_4
|
||||
assert percentage_to_ordered_list_item(LARGE_ORDERED_LIST, 56) == SPEED_4
|
||||
assert percentage_to_ordered_list_item(LARGE_ORDERED_LIST, 50) == SPEED_4
|
||||
assert percentage_to_ordered_list_item(LARGE_ORDERED_LIST, 51) == SPEED_4
|
||||
assert percentage_to_ordered_list_item(LARGE_ORDERED_LIST, 75) == SPEED_6
|
||||
assert percentage_to_ordered_list_item(LARGE_ORDERED_LIST, 76) == SPEED_6
|
||||
assert percentage_to_ordered_list_item(LARGE_ORDERED_LIST, 100) == SPEED_7
|
||||
|
||||
assert percentage_to_ordered_list_item(LARGE_ORDERED_LIST, 1) == SPEED_1
|
||||
assert percentage_to_ordered_list_item(LARGE_ORDERED_LIST, 25) == SPEED_2
|
||||
assert percentage_to_ordered_list_item(LARGE_ORDERED_LIST, 26) == SPEED_2
|
||||
assert percentage_to_ordered_list_item(LARGE_ORDERED_LIST, 50) == SPEED_4
|
||||
assert percentage_to_ordered_list_item(LARGE_ORDERED_LIST, 51) == SPEED_4
|
||||
assert percentage_to_ordered_list_item(LARGE_ORDERED_LIST, 75) == SPEED_6
|
||||
assert percentage_to_ordered_list_item(LARGE_ORDERED_LIST, 76) == SPEED_6
|
||||
assert percentage_to_ordered_list_item(LARGE_ORDERED_LIST, 100) == SPEED_7
|
||||
|
||||
assert percentage_to_ordered_list_item(LARGE_ORDERED_LIST, 100.1) == SPEED_7
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
assert percentage_to_ordered_list_item([], 100)
|
||||
|
||||
|
||||
async def test_ranged_value_to_percentage_large():
|
||||
"""Test a large range of low and high values convert a single value to a percentage."""
|
||||
range = (1, 255)
|
||||
|
||||
assert ranged_value_to_percentage(range, 255) == 100
|
||||
assert ranged_value_to_percentage(range, 127) == 49
|
||||
assert ranged_value_to_percentage(range, 10) == 3
|
||||
assert ranged_value_to_percentage(range, 1) == 0
|
||||
|
||||
|
||||
async def test_percentage_to_ranged_value_large():
|
||||
"""Test a large range of low and high values convert a percentage to a single value."""
|
||||
range = (1, 255)
|
||||
|
||||
assert percentage_to_ranged_value(range, 100) == 255
|
||||
assert percentage_to_ranged_value(range, 50) == 127.5
|
||||
assert percentage_to_ranged_value(range, 4) == 10.2
|
||||
|
||||
assert math.ceil(percentage_to_ranged_value(range, 100)) == 255
|
||||
assert math.ceil(percentage_to_ranged_value(range, 50)) == 128
|
||||
assert math.ceil(percentage_to_ranged_value(range, 4)) == 11
|
||||
|
||||
|
||||
async def test_ranged_value_to_percentage_small():
|
||||
"""Test a small range of low and high values convert a single value to a percentage."""
|
||||
range = (1, 6)
|
||||
|
||||
assert ranged_value_to_percentage(range, 1) == 16
|
||||
assert ranged_value_to_percentage(range, 2) == 33
|
||||
assert ranged_value_to_percentage(range, 3) == 50
|
||||
assert ranged_value_to_percentage(range, 4) == 66
|
||||
assert ranged_value_to_percentage(range, 5) == 83
|
||||
assert ranged_value_to_percentage(range, 6) == 100
|
||||
|
||||
|
||||
async def test_percentage_to_ranged_value_small():
|
||||
"""Test a small range of low and high values convert a percentage to a single value."""
|
||||
range = (1, 6)
|
||||
|
||||
assert math.ceil(percentage_to_ranged_value(range, 16)) == 1
|
||||
assert math.ceil(percentage_to_ranged_value(range, 33)) == 2
|
||||
assert math.ceil(percentage_to_ranged_value(range, 50)) == 3
|
||||
assert math.ceil(percentage_to_ranged_value(range, 66)) == 4
|
||||
assert math.ceil(percentage_to_ranged_value(range, 83)) == 5
|
||||
assert math.ceil(percentage_to_ranged_value(range, 100)) == 6
|
Loading…
Reference in New Issue