mirror of
https://github.com/home-assistant/core.git
synced 2025-07-24 21:57:51 +00:00
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>
This commit is contained in:
parent
3f948e027a
commit
068d1b5eb8
@ -118,7 +118,20 @@ class BondFan(BondEntity, FanEntity):
|
|||||||
self._device.device_id, Action.set_speed(bond_speed)
|
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."""
|
"""Turn on the fan."""
|
||||||
_LOGGER.debug("Fan async_turn_on called with speed %s", speed)
|
_LOGGER.debug("Fan async_turn_on called with speed %s", speed)
|
||||||
|
|
||||||
|
@ -102,7 +102,16 @@ class ComfoConnectFan(FanEntity):
|
|||||||
"""List of available fan modes."""
|
"""List of available fan modes."""
|
||||||
return [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
|
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."""
|
"""Turn on the fan."""
|
||||||
if speed is None:
|
if speed is None:
|
||||||
speed = SPEED_LOW
|
speed = SPEED_LOW
|
||||||
|
@ -107,7 +107,20 @@ class DeconzFan(DeconzDevice, FanEntity):
|
|||||||
|
|
||||||
await self._device.set_speed(SPEEDS[speed])
|
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."""
|
"""Turn on fan."""
|
||||||
if not speed:
|
if not speed:
|
||||||
speed = convert_speed(self._default_on_speed)
|
speed = convert_speed(self._default_on_speed)
|
||||||
|
@ -1,14 +1,20 @@
|
|||||||
"""Demo fan platform that has a fake fan."""
|
"""Demo fan platform that has a fake fan."""
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
from homeassistant.components.fan import (
|
from homeassistant.components.fan import (
|
||||||
SPEED_HIGH,
|
SPEED_HIGH,
|
||||||
SPEED_LOW,
|
SPEED_LOW,
|
||||||
SPEED_MEDIUM,
|
SPEED_MEDIUM,
|
||||||
|
SPEED_OFF,
|
||||||
SUPPORT_DIRECTION,
|
SUPPORT_DIRECTION,
|
||||||
SUPPORT_OSCILLATE,
|
SUPPORT_OSCILLATE,
|
||||||
|
SUPPORT_PRESET_MODE,
|
||||||
SUPPORT_SET_SPEED,
|
SUPPORT_SET_SPEED,
|
||||||
FanEntity,
|
FanEntity,
|
||||||
)
|
)
|
||||||
from homeassistant.const import STATE_OFF
|
|
||||||
|
PRESET_MODE_AUTO = "auto"
|
||||||
|
PRESET_MODE_SMART = "smart"
|
||||||
|
|
||||||
FULL_SUPPORT = SUPPORT_SET_SPEED | SUPPORT_OSCILLATE | SUPPORT_DIRECTION
|
FULL_SUPPORT = SUPPORT_SET_SPEED | SUPPORT_OSCILLATE | SUPPORT_DIRECTION
|
||||||
LIMITED_SUPPORT = SUPPORT_SET_SPEED
|
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."""
|
"""Set up the demo fan platform."""
|
||||||
async_add_entities(
|
async_add_entities(
|
||||||
[
|
[
|
||||||
DemoFan(hass, "fan1", "Living Room Fan", FULL_SUPPORT),
|
# These fans implement the old model
|
||||||
DemoFan(hass, "fan2", "Ceiling Fan", LIMITED_SUPPORT),
|
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)
|
await async_setup_platform(hass, {}, async_add_entities)
|
||||||
|
|
||||||
|
|
||||||
class DemoFan(FanEntity):
|
class BaseDemoFan(FanEntity):
|
||||||
"""A demonstration fan component."""
|
"""A demonstration fan component that uses legacy fan speeds."""
|
||||||
|
|
||||||
def __init__(
|
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:
|
) -> None:
|
||||||
"""Initialize the entity."""
|
"""Initialize the entity."""
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
self._unique_id = unique_id
|
self._unique_id = unique_id
|
||||||
self._supported_features = supported_features
|
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._oscillating = None
|
||||||
self._direction = None
|
self._direction = None
|
||||||
self._name = name
|
self._name = name
|
||||||
|
|
||||||
if supported_features & SUPPORT_OSCILLATE:
|
if supported_features & SUPPORT_OSCILLATE:
|
||||||
self._oscillating = False
|
self._oscillating = False
|
||||||
if supported_features & SUPPORT_DIRECTION:
|
if supported_features & SUPPORT_DIRECTION:
|
||||||
@ -64,17 +126,42 @@ class DemoFan(FanEntity):
|
|||||||
"""No polling needed for a demo fan."""
|
"""No polling needed for a demo fan."""
|
||||||
return False
|
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
|
@property
|
||||||
def speed(self) -> str:
|
def speed(self) -> str:
|
||||||
"""Return the current speed."""
|
"""Return the current speed."""
|
||||||
return self._speed
|
return self._speed
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def speed_list(self) -> list:
|
def speed_list(self):
|
||||||
"""Get the list of available speeds."""
|
"""Return the speed list."""
|
||||||
return [STATE_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
|
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."""
|
"""Turn on the entity."""
|
||||||
if speed is None:
|
if speed is None:
|
||||||
speed = SPEED_MEDIUM
|
speed = SPEED_MEDIUM
|
||||||
@ -83,7 +170,7 @@ class DemoFan(FanEntity):
|
|||||||
def turn_off(self, **kwargs) -> None:
|
def turn_off(self, **kwargs) -> None:
|
||||||
"""Turn off the entity."""
|
"""Turn off the entity."""
|
||||||
self.oscillate(False)
|
self.oscillate(False)
|
||||||
self.set_speed(STATE_OFF)
|
self.set_speed(SPEED_OFF)
|
||||||
|
|
||||||
def set_speed(self, speed: str) -> None:
|
def set_speed(self, speed: str) -> None:
|
||||||
"""Set the speed of the fan."""
|
"""Set the speed of the fan."""
|
||||||
@ -100,17 +187,124 @@ class DemoFan(FanEntity):
|
|||||||
self._oscillating = oscillating
|
self._oscillating = oscillating
|
||||||
self.schedule_update_ha_state()
|
self.schedule_update_ha_state()
|
||||||
|
|
||||||
@property
|
|
||||||
def current_direction(self) -> str:
|
class DemoPercentageFan(BaseDemoFan, FanEntity):
|
||||||
"""Fan direction."""
|
"""A demonstration fan component that uses percentages."""
|
||||||
return self._direction
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def oscillating(self) -> bool:
|
def percentage(self) -> str:
|
||||||
"""Oscillating."""
|
"""Return the current speed."""
|
||||||
return self._oscillating
|
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
|
@property
|
||||||
def supported_features(self) -> int:
|
def preset_mode(self) -> Optional[str]:
|
||||||
"""Flag supported features."""
|
"""Return the current preset mode, e.g., auto, smart, interval, favorite."""
|
||||||
return self._supported_features
|
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."""
|
"""Initialize the fan."""
|
||||||
super().__init__(device, DysonPureCoolState)
|
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."""
|
"""Turn on the fan."""
|
||||||
_LOGGER.debug("Turn on fan %s with speed %s", self.name, speed)
|
_LOGGER.debug("Turn on fan %s with speed %s", self.name, speed)
|
||||||
if speed is not None:
|
if speed is not None:
|
||||||
@ -299,7 +312,20 @@ class DysonPureCoolEntity(DysonFanEntity):
|
|||||||
"""Initialize the fan."""
|
"""Initialize the fan."""
|
||||||
super().__init__(device, DysonPureCoolV2State)
|
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."""
|
"""Turn on the fan."""
|
||||||
_LOGGER.debug("Turn on fan %s", self.name)
|
_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)
|
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."""
|
"""Turn on the fan."""
|
||||||
if speed == SPEED_OFF:
|
if speed == SPEED_OFF:
|
||||||
await self.async_turn_off()
|
await self.async_turn_off()
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import functools as ft
|
import functools as ft
|
||||||
import logging
|
import logging
|
||||||
from typing import Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
import voluptuous as vol
|
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 import ToggleEntity
|
||||||
from homeassistant.helpers.entity_component import EntityComponent
|
from homeassistant.helpers.entity_component import EntityComponent
|
||||||
from homeassistant.loader import bind_hass
|
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__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -32,10 +36,13 @@ ENTITY_ID_FORMAT = DOMAIN + ".{}"
|
|||||||
SUPPORT_SET_SPEED = 1
|
SUPPORT_SET_SPEED = 1
|
||||||
SUPPORT_OSCILLATE = 2
|
SUPPORT_OSCILLATE = 2
|
||||||
SUPPORT_DIRECTION = 4
|
SUPPORT_DIRECTION = 4
|
||||||
|
SUPPORT_PRESET_MODE = 8
|
||||||
|
|
||||||
SERVICE_SET_SPEED = "set_speed"
|
SERVICE_SET_SPEED = "set_speed"
|
||||||
SERVICE_OSCILLATE = "oscillate"
|
SERVICE_OSCILLATE = "oscillate"
|
||||||
SERVICE_SET_DIRECTION = "set_direction"
|
SERVICE_SET_DIRECTION = "set_direction"
|
||||||
|
SERVICE_SET_PERCENTAGE = "set_percentage"
|
||||||
|
SERVICE_SET_PRESET_MODE = "set_preset_mode"
|
||||||
|
|
||||||
SPEED_OFF = "off"
|
SPEED_OFF = "off"
|
||||||
SPEED_LOW = "low"
|
SPEED_LOW = "low"
|
||||||
@ -46,9 +53,47 @@ DIRECTION_FORWARD = "forward"
|
|||||||
DIRECTION_REVERSE = "reverse"
|
DIRECTION_REVERSE = "reverse"
|
||||||
|
|
||||||
ATTR_SPEED = "speed"
|
ATTR_SPEED = "speed"
|
||||||
|
ATTR_PERCENTAGE = "percentage"
|
||||||
ATTR_SPEED_LIST = "speed_list"
|
ATTR_SPEED_LIST = "speed_list"
|
||||||
ATTR_OSCILLATING = "oscillating"
|
ATTR_OSCILLATING = "oscillating"
|
||||||
ATTR_DIRECTION = "direction"
|
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
|
@bind_hass
|
||||||
@ -56,7 +101,7 @@ def is_on(hass, entity_id: str) -> bool:
|
|||||||
"""Return if the fans are on based on the statemachine."""
|
"""Return if the fans are on based on the statemachine."""
|
||||||
state = hass.states.get(entity_id)
|
state = hass.states.get(entity_id)
|
||||||
if ATTR_SPEED in state.attributes:
|
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
|
return state.state == STATE_ON
|
||||||
|
|
||||||
|
|
||||||
@ -68,15 +113,27 @@ async def async_setup(hass, config: dict):
|
|||||||
|
|
||||||
await component.async_setup(config)
|
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(
|
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_TURN_OFF, {}, "async_turn_off")
|
||||||
component.async_register_entity_service(SERVICE_TOGGLE, {}, "async_toggle")
|
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(
|
component.async_register_entity_service(
|
||||||
SERVICE_SET_SPEED,
|
SERVICE_SET_SPEED,
|
||||||
{vol.Required(ATTR_SPEED): cv.string},
|
{vol.Required(ATTR_SPEED): cv.string},
|
||||||
"async_set_speed",
|
"async_set_speed_deprecated",
|
||||||
[SUPPORT_SET_SPEED],
|
[SUPPORT_SET_SPEED],
|
||||||
)
|
)
|
||||||
component.async_register_entity_service(
|
component.async_register_entity_service(
|
||||||
@ -91,6 +148,22 @@ async def async_setup(hass, config: dict):
|
|||||||
"async_set_direction",
|
"async_set_direction",
|
||||||
[SUPPORT_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
|
return True
|
||||||
|
|
||||||
@ -105,19 +178,91 @@ async def async_unload_entry(hass, entry):
|
|||||||
return await hass.data[DOMAIN].async_unload_entry(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):
|
class FanEntity(ToggleEntity):
|
||||||
"""Representation of a fan."""
|
"""Representation of a fan."""
|
||||||
|
|
||||||
|
@_fan_native
|
||||||
def set_speed(self, speed: str) -> None:
|
def set_speed(self, speed: str) -> None:
|
||||||
"""Set the speed of the fan."""
|
"""Set the speed of the fan."""
|
||||||
raise NotImplementedError()
|
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):
|
async def async_set_speed(self, speed: str):
|
||||||
"""Set the speed of the fan."""
|
"""Set the speed of the fan."""
|
||||||
if speed == SPEED_OFF:
|
if speed == SPEED_OFF:
|
||||||
await self.async_turn_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:
|
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:
|
def set_direction(self, direction: str) -> None:
|
||||||
"""Set the direction of the fan."""
|
"""Set the direction of the fan."""
|
||||||
@ -128,18 +273,75 @@ class FanEntity(ToggleEntity):
|
|||||||
await self.hass.async_add_executor_job(self.set_direction, direction)
|
await self.hass.async_add_executor_job(self.set_direction, direction)
|
||||||
|
|
||||||
# pylint: disable=arguments-differ
|
# 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."""
|
"""Turn on the fan."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
# pylint: disable=arguments-differ
|
# 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."""
|
"""Turn on the fan."""
|
||||||
if speed == SPEED_OFF:
|
if speed == SPEED_OFF:
|
||||||
await self.async_turn_off()
|
await self.async_turn_off()
|
||||||
else:
|
else:
|
||||||
await self.hass.async_add_executor_job(
|
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:
|
def oscillate(self, oscillating: bool) -> None:
|
||||||
@ -155,15 +357,57 @@ class FanEntity(ToggleEntity):
|
|||||||
"""Return true if the entity is on."""
|
"""Return true if the entity is on."""
|
||||||
return self.speed not in [SPEED_OFF, None]
|
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
|
@property
|
||||||
def speed(self) -> Optional[str]:
|
def speed(self) -> Optional[str]:
|
||||||
"""Return the current speed."""
|
"""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
|
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
|
@property
|
||||||
def speed_list(self) -> list:
|
def speed_list(self) -> list:
|
||||||
"""Get the list of available speeds."""
|
"""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
|
@property
|
||||||
def current_direction(self) -> Optional[str]:
|
def current_direction(self) -> Optional[str]:
|
||||||
@ -178,9 +422,79 @@ class FanEntity(ToggleEntity):
|
|||||||
@property
|
@property
|
||||||
def capability_attributes(self):
|
def capability_attributes(self):
|
||||||
"""Return capability attributes."""
|
"""Return capability attributes."""
|
||||||
|
attrs = {}
|
||||||
if self.supported_features & SUPPORT_SET_SPEED:
|
if self.supported_features & SUPPORT_SET_SPEED:
|
||||||
return {ATTR_SPEED_LIST: self.speed_list}
|
attrs[ATTR_SPEED_LIST] = self.speed_list
|
||||||
return {}
|
|
||||||
|
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
|
@property
|
||||||
def state_attributes(self) -> dict:
|
def state_attributes(self) -> dict:
|
||||||
@ -196,6 +510,13 @@ class FanEntity(ToggleEntity):
|
|||||||
|
|
||||||
if supported_features & SUPPORT_SET_SPEED:
|
if supported_features & SUPPORT_SET_SPEED:
|
||||||
data[ATTR_SPEED] = self.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
|
return data
|
||||||
|
|
||||||
@ -203,3 +524,72 @@ class FanEntity(ToggleEntity):
|
|||||||
def supported_features(self) -> int:
|
def supported_features(self) -> int:
|
||||||
"""Flag supported features."""
|
"""Flag supported features."""
|
||||||
return 0
|
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 (
|
from . import (
|
||||||
ATTR_DIRECTION,
|
ATTR_DIRECTION,
|
||||||
ATTR_OSCILLATING,
|
ATTR_OSCILLATING,
|
||||||
|
ATTR_PERCENTAGE,
|
||||||
|
ATTR_PRESET_MODE,
|
||||||
ATTR_SPEED,
|
ATTR_SPEED,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
SERVICE_OSCILLATE,
|
SERVICE_OSCILLATE,
|
||||||
SERVICE_SET_DIRECTION,
|
SERVICE_SET_DIRECTION,
|
||||||
|
SERVICE_SET_PERCENTAGE,
|
||||||
|
SERVICE_SET_PRESET_MODE,
|
||||||
SERVICE_SET_SPEED,
|
SERVICE_SET_SPEED,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -31,6 +35,8 @@ ATTRIBUTES = { # attribute: service
|
|||||||
ATTR_DIRECTION: SERVICE_SET_DIRECTION,
|
ATTR_DIRECTION: SERVICE_SET_DIRECTION,
|
||||||
ATTR_OSCILLATING: SERVICE_OSCILLATE,
|
ATTR_OSCILLATING: SERVICE_OSCILLATE,
|
||||||
ATTR_SPEED: SERVICE_SET_SPEED,
|
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
|
description: Speed setting
|
||||||
example: "low"
|
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:
|
turn_on:
|
||||||
description: Turns fan on.
|
description: Turns fan on.
|
||||||
fields:
|
fields:
|
||||||
@ -18,6 +38,12 @@ turn_on:
|
|||||||
speed:
|
speed:
|
||||||
description: Speed setting
|
description: Speed setting
|
||||||
example: "high"
|
example: "high"
|
||||||
|
percentage:
|
||||||
|
description: Percentage speed setting
|
||||||
|
example: 75
|
||||||
|
preset_mode:
|
||||||
|
description: Preset mode setting
|
||||||
|
example: "auto"
|
||||||
|
|
||||||
turn_off:
|
turn_off:
|
||||||
description: Turns fan off.
|
description: Turns fan off.
|
||||||
|
@ -130,9 +130,17 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity):
|
|||||||
{CharacteristicsTypes.SWING_MODE: 1 if oscillating else 0}
|
{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."""
|
"""Turn the specified fan on."""
|
||||||
|
|
||||||
characteristics = {}
|
characteristics = {}
|
||||||
|
|
||||||
if not self.is_on:
|
if not self.is_on:
|
||||||
|
@ -63,7 +63,20 @@ class InsteonFanEntity(InsteonEntity, FanEntity):
|
|||||||
"""Flag supported features."""
|
"""Flag supported features."""
|
||||||
return SUPPORT_SET_SPEED
|
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."""
|
"""Turn on the fan."""
|
||||||
if speed is None:
|
if speed is None:
|
||||||
speed = SPEED_MEDIUM
|
speed = SPEED_MEDIUM
|
||||||
|
@ -71,7 +71,20 @@ class ISYFanEntity(ISYNodeEntity, FanEntity):
|
|||||||
"""Send the set speed command to the ISY994 fan device."""
|
"""Send the set speed command to the ISY994 fan device."""
|
||||||
self._node.turn_on(val=STATE_TO_VALUE.get(speed, 255))
|
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."""
|
"""Send the turn on command to the ISY994 fan device."""
|
||||||
self.set_speed(speed)
|
self.set_speed(speed)
|
||||||
|
|
||||||
@ -108,7 +121,20 @@ class ISYFanProgramEntity(ISYProgramEntity, FanEntity):
|
|||||||
if not self._actions.run_then():
|
if not self._actions.run_then():
|
||||||
_LOGGER.error("Unable to turn off the fan")
|
_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."""
|
"""Send the turn off command to ISY994 fan program."""
|
||||||
if not self._actions.run_else():
|
if not self._actions.run_else():
|
||||||
_LOGGER.error("Unable to turn on the fan")
|
_LOGGER.error("Unable to turn on the fan")
|
||||||
|
@ -75,7 +75,20 @@ class LutronCasetaFan(LutronCasetaDevice, FanEntity):
|
|||||||
"""Flag supported features. Speed Only."""
|
"""Flag supported features. Speed Only."""
|
||||||
return SUPPORT_SET_SPEED
|
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."""
|
"""Turn the fan on."""
|
||||||
if speed is None:
|
if speed is None:
|
||||||
speed = SPEED_MEDIUM
|
speed = SPEED_MEDIUM
|
||||||
|
@ -317,7 +317,20 @@ class MqttFan(MqttEntity, FanEntity):
|
|||||||
"""Return the oscillation state."""
|
"""Return the oscillation state."""
|
||||||
return self._oscillation
|
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.
|
"""Turn on the entity.
|
||||||
|
|
||||||
This method is a coroutine.
|
This method is a coroutine.
|
||||||
|
@ -57,7 +57,16 @@ class ZwaveFan(ZWaveDeviceEntity, FanEntity):
|
|||||||
self._previous_speed = speed
|
self._previous_speed = speed
|
||||||
self.values.primary.send_value(SPEED_TO_VALUE[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."""
|
"""Turn the device on."""
|
||||||
if speed is None:
|
if speed is None:
|
||||||
# Value 255 tells device to return to previous value
|
# 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
|
# the entity state ahead of receiving the confirming push updates
|
||||||
self.async_write_ha_state()
|
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."""
|
"""Turn the fan on."""
|
||||||
if speed is not None:
|
if speed is not None:
|
||||||
value = SPEED_TO_VALUE[speed]
|
value = SPEED_TO_VALUE[speed]
|
||||||
|
@ -86,7 +86,14 @@ class SmartyFan(FanEntity):
|
|||||||
self._speed = speed
|
self._speed = speed
|
||||||
self._state = True
|
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."""
|
"""Turn on the fan."""
|
||||||
_LOGGER.debug("Turning on fan. Speed is %s", speed)
|
_LOGGER.debug("Turning on fan. Speed is %s", speed)
|
||||||
if speed is None:
|
if speed is None:
|
||||||
|
@ -79,7 +79,16 @@ class TasmotaFan(
|
|||||||
else:
|
else:
|
||||||
self._tasmota_entity.set_speed(HA_TO_TASMOTA_SPEED_MAP[speed])
|
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."""
|
"""Turn the fan on."""
|
||||||
# Tasmota does not support turning a fan on with implicit speed
|
# Tasmota does not support turning a fan on with implicit speed
|
||||||
await self.async_set_speed(speed or fan.SPEED_MEDIUM)
|
await self.async_set_speed(speed or fan.SPEED_MEDIUM)
|
||||||
|
@ -251,8 +251,20 @@ class TemplateFan(TemplateEntity, FanEntity):
|
|||||||
"""Return the oscillation state."""
|
"""Return the oscillation state."""
|
||||||
return self._direction
|
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."""
|
"""Turn on the fan."""
|
||||||
await self._on_script.async_run({ATTR_SPEED: speed}, context=self._context)
|
await self._on_script.async_run({ATTR_SPEED: speed}, context=self._context)
|
||||||
self._state = STATE_ON
|
self._state = STATE_ON
|
||||||
|
@ -75,7 +75,20 @@ class TuyaFanDevice(TuyaDevice, FanEntity):
|
|||||||
else:
|
else:
|
||||||
self._tuya.set_speed(speed)
|
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."""
|
"""Turn on the fan."""
|
||||||
if speed is not None:
|
if speed is not None:
|
||||||
self.set_speed(speed)
|
self.set_speed(speed)
|
||||||
|
@ -137,7 +137,20 @@ class ValloxFan(FanEntity):
|
|||||||
self._available = False
|
self._available = False
|
||||||
_LOGGER.error("Error updating fan: %s", err)
|
_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."""
|
"""Turn the device on."""
|
||||||
_LOGGER.debug("Turn on: %s", speed)
|
_LOGGER.debug("Turn on: %s", speed)
|
||||||
|
|
||||||
|
@ -107,7 +107,20 @@ class VeSyncFanHA(VeSyncDevice, FanEntity):
|
|||||||
self.smartfan.manual_mode()
|
self.smartfan.manual_mode()
|
||||||
self.smartfan.change_fan_speed(FAN_SPEEDS.index(speed))
|
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."""
|
"""Turn the device on."""
|
||||||
self.smartfan.turn_on()
|
self.smartfan.turn_on()
|
||||||
self.set_speed(speed)
|
self.set_speed(speed)
|
||||||
|
@ -185,7 +185,20 @@ class WemoHumidifier(WemoSubscriptionEntity, FanEntity):
|
|||||||
self._available = False
|
self._available = False
|
||||||
self.wemo.reconnect_with_device()
|
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."""
|
"""Turn the switch on."""
|
||||||
if speed is None:
|
if speed is None:
|
||||||
try:
|
try:
|
||||||
|
@ -94,7 +94,20 @@ class WiLightFan(WiLightDevice, FanEntity):
|
|||||||
self._direction = self._status["direction"]
|
self._direction = self._status["direction"]
|
||||||
return self._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."""
|
"""Turn on the fan."""
|
||||||
if speed is None:
|
if speed is None:
|
||||||
await self._client.set_fan_direction(self._index, self._direction)
|
await self._client.set_fan_direction(self._index, self._direction)
|
||||||
|
@ -40,7 +40,20 @@ class WinkFanDevice(WinkDevice, FanEntity):
|
|||||||
"""Set the speed of the fan."""
|
"""Set the speed of the fan."""
|
||||||
self.wink.set_state(True, speed)
|
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."""
|
"""Turn on the fan."""
|
||||||
self.wink.set_state(True, speed)
|
self.wink.set_state(True, speed)
|
||||||
|
|
||||||
|
@ -718,7 +718,20 @@ class XiaomiGenericDevice(FanEntity):
|
|||||||
|
|
||||||
return False
|
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."""
|
"""Turn the device on."""
|
||||||
if speed:
|
if speed:
|
||||||
# If operation mode was set the device must not be turned on.
|
# If operation mode was set the device must not be turned on.
|
||||||
|
@ -95,7 +95,16 @@ class BaseFan(FanEntity):
|
|||||||
"""Flag supported features."""
|
"""Flag supported features."""
|
||||||
return SUPPORT_SET_SPEED
|
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."""
|
"""Turn the entity on."""
|
||||||
if speed is None:
|
if speed is None:
|
||||||
speed = SPEED_MEDIUM
|
speed = SPEED_MEDIUM
|
||||||
|
@ -58,7 +58,14 @@ class ZwaveFan(ZWaveDeviceEntity, FanEntity):
|
|||||||
"""Set the speed of the fan."""
|
"""Set the speed of the fan."""
|
||||||
self.node.set_dimmer(self.values.primary.value_id, SPEED_TO_VALUE[speed])
|
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."""
|
"""Turn the device on."""
|
||||||
if speed is None:
|
if speed is None:
|
||||||
# Value 255 tells device to return to previous value
|
# Value 255 tells device to return to previous value
|
||||||
|
@ -72,7 +72,20 @@ class ZwaveFan(ZWaveBaseEntity, FanEntity):
|
|||||||
target_value = self.get_zwave_value("targetValue")
|
target_value = self.get_zwave_value("targetValue")
|
||||||
await self.info.node.async_set_value(target_value, SPEED_TO_VALUE[speed])
|
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."""
|
"""Turn the device on."""
|
||||||
if speed is None:
|
if speed is None:
|
||||||
# Value 255 tells device to return to previous value
|
# Value 255 tells device to return to previous value
|
||||||
|
87
homeassistant/util/percentage.py
Normal file
87
homeassistant/util/percentage.py
Normal file
@ -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
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components import fan
|
from homeassistant.components import fan
|
||||||
|
from homeassistant.components.demo.fan import PRESET_MODE_AUTO, PRESET_MODE_SMART
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID,
|
ATTR_ENTITY_ID,
|
||||||
ENTITY_MATCH_ALL,
|
ENTITY_MATCH_ALL,
|
||||||
@ -12,7 +13,15 @@ from homeassistant.const import (
|
|||||||
)
|
)
|
||||||
from homeassistant.setup import async_setup_component
|
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)
|
@pytest.fixture(autouse=True)
|
||||||
@ -22,124 +31,338 @@ async def setup_comp(hass):
|
|||||||
await hass.async_block_till_done()
|
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."""
|
"""Test turning on the device."""
|
||||||
state = hass.states.get(FAN_ENTITY_ID)
|
state = hass.states.get(fan_entity_id)
|
||||||
assert state.state == STATE_OFF
|
assert state.state == STATE_OFF
|
||||||
|
|
||||||
await hass.services.async_call(
|
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
|
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(
|
await hass.services.async_call(
|
||||||
fan.DOMAIN,
|
fan.DOMAIN,
|
||||||
SERVICE_TURN_ON,
|
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,
|
blocking=True,
|
||||||
)
|
)
|
||||||
state = hass.states.get(FAN_ENTITY_ID)
|
state = hass.states.get(fan_entity_id)
|
||||||
assert state.state == STATE_ON
|
assert state.state == STATE_ON
|
||||||
assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_HIGH
|
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."""
|
"""Test turning off the device."""
|
||||||
state = hass.states.get(FAN_ENTITY_ID)
|
state = hass.states.get(fan_entity_id)
|
||||||
assert state.state == STATE_OFF
|
assert state.state == STATE_OFF
|
||||||
|
|
||||||
await hass.services.async_call(
|
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
|
assert state.state == STATE_ON
|
||||||
|
|
||||||
await hass.services.async_call(
|
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
|
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."""
|
"""Test turning off all fans."""
|
||||||
state = hass.states.get(FAN_ENTITY_ID)
|
state = hass.states.get(fan_entity_id)
|
||||||
assert state.state == STATE_OFF
|
assert state.state == STATE_OFF
|
||||||
|
|
||||||
await hass.services.async_call(
|
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
|
assert state.state == STATE_ON
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
fan.DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: ENTITY_MATCH_ALL}, blocking=True
|
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
|
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."""
|
"""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
|
assert state.state == STATE_OFF
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
fan.DOMAIN,
|
fan.DOMAIN,
|
||||||
fan.SERVICE_SET_DIRECTION,
|
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,
|
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
|
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."""
|
"""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
|
assert state.state == STATE_OFF
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
fan.DOMAIN,
|
fan.DOMAIN,
|
||||||
fan.SERVICE_SET_SPEED,
|
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,
|
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
|
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."""
|
"""Test oscillating the fan."""
|
||||||
state = hass.states.get(FAN_ENTITY_ID)
|
state = hass.states.get(fan_entity_id)
|
||||||
assert state.state == STATE_OFF
|
assert state.state == STATE_OFF
|
||||||
assert not state.attributes.get(fan.ATTR_OSCILLATING)
|
assert not state.attributes.get(fan.ATTR_OSCILLATING)
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
fan.DOMAIN,
|
fan.DOMAIN,
|
||||||
fan.SERVICE_OSCILLATE,
|
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,
|
blocking=True,
|
||||||
)
|
)
|
||||||
state = hass.states.get(FAN_ENTITY_ID)
|
state = hass.states.get(fan_entity_id)
|
||||||
assert state.attributes[fan.ATTR_OSCILLATING] is True
|
assert state.attributes[fan.ATTR_OSCILLATING] is True
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
fan.DOMAIN,
|
fan.DOMAIN,
|
||||||
fan.SERVICE_OSCILLATE,
|
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,
|
blocking=True,
|
||||||
)
|
)
|
||||||
state = hass.states.get(FAN_ENTITY_ID)
|
state = hass.states.get(fan_entity_id)
|
||||||
assert state.attributes[fan.ATTR_OSCILLATING] is False
|
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."""
|
"""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(
|
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",
|
"8": "media_player.lounge_room",
|
||||||
"9": "fan.living_room_fan",
|
"9": "fan.living_room_fan",
|
||||||
"10": "fan.ceiling_fan",
|
"10": "fan.ceiling_fan",
|
||||||
"11": "cover.living_room_window",
|
"11": "fan.percentage_full_fan",
|
||||||
"12": "climate.hvac",
|
"12": "fan.percentage_limited_fan",
|
||||||
"13": "climate.heatpump",
|
"13": "fan.preset_only_limited_fan",
|
||||||
"14": "climate.ecobee",
|
"14": "cover.living_room_window",
|
||||||
"15": "light.no_brightness",
|
"15": "climate.hvac",
|
||||||
"16": "humidifier.humidifier",
|
"16": "climate.heatpump",
|
||||||
"17": "humidifier.dehumidifier",
|
"17": "climate.ecobee",
|
||||||
"18": "humidifier.hygrostat",
|
"18": "light.no_brightness",
|
||||||
"19": "scene.light_on",
|
"19": "humidifier.humidifier",
|
||||||
"20": "scene.light_off",
|
"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()}
|
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 (
|
from homeassistant.components.fan import (
|
||||||
ATTR_DIRECTION,
|
ATTR_DIRECTION,
|
||||||
ATTR_OSCILLATING,
|
ATTR_OSCILLATING,
|
||||||
|
ATTR_PERCENTAGE,
|
||||||
|
ATTR_PRESET_MODE,
|
||||||
ATTR_SPEED,
|
ATTR_SPEED,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
SERVICE_OSCILLATE,
|
SERVICE_OSCILLATE,
|
||||||
SERVICE_SET_DIRECTION,
|
SERVICE_SET_DIRECTION,
|
||||||
|
SERVICE_SET_PERCENTAGE,
|
||||||
|
SERVICE_SET_PRESET_MODE,
|
||||||
SERVICE_SET_SPEED,
|
SERVICE_SET_SPEED,
|
||||||
)
|
)
|
||||||
from homeassistant.const import (
|
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."""
|
"""Turn all or specified fan on."""
|
||||||
data = {
|
data = {
|
||||||
key: value
|
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
|
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)
|
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(
|
async def async_set_direction(
|
||||||
hass, entity_id=ENTITY_MATCH_ALL, direction: str = None
|
hass, entity_id=ENTITY_MATCH_ALL, direction: str = None
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.fan import FanEntity
|
from homeassistant.components.fan import FanEntity, NotValidPresetModeError
|
||||||
|
|
||||||
|
|
||||||
class BaseFan(FanEntity):
|
class BaseFan(FanEntity):
|
||||||
@ -17,6 +17,7 @@ def test_fanentity():
|
|||||||
fan = BaseFan()
|
fan = BaseFan()
|
||||||
assert fan.state == "off"
|
assert fan.state == "off"
|
||||||
assert len(fan.speed_list) == 0
|
assert len(fan.speed_list) == 0
|
||||||
|
assert len(fan.preset_modes) == 0
|
||||||
assert fan.supported_features == 0
|
assert fan.supported_features == 0
|
||||||
assert fan.capability_attributes == {}
|
assert fan.capability_attributes == {}
|
||||||
# Test set_speed not required
|
# Test set_speed not required
|
||||||
@ -24,7 +25,35 @@ def test_fanentity():
|
|||||||
fan.oscillate(True)
|
fan.oscillate(True)
|
||||||
with pytest.raises(NotImplementedError):
|
with pytest.raises(NotImplementedError):
|
||||||
fan.set_speed("slow")
|
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):
|
with pytest.raises(NotImplementedError):
|
||||||
fan.turn_on()
|
fan.turn_on()
|
||||||
with pytest.raises(NotImplementedError):
|
with pytest.raises(NotImplementedError):
|
||||||
fan.turn_off()
|
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",
|
"type": "action.devices.types.FAN",
|
||||||
"willReportState": False,
|
"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",
|
"id": "climate.hvac",
|
||||||
"name": {"name": "Hvac"},
|
"name": {"name": "Hvac"},
|
||||||
|
158
tests/util/test_percentage.py
Normal file
158
tests/util/test_percentage.py
Normal file
@ -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…
x
Reference in New Issue
Block a user