Implement percentage step sizes for fans (#46512)
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
This commit is contained in:
parent
5df46b60e8
commit
f2b303d509
24 changed files with 447 additions and 3 deletions
|
@ -16,6 +16,7 @@ from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.util.percentage import (
|
from homeassistant.util.percentage import (
|
||||||
|
int_states_in_range,
|
||||||
percentage_to_ranged_value,
|
percentage_to_ranged_value,
|
||||||
ranged_value_to_percentage,
|
ranged_value_to_percentage,
|
||||||
)
|
)
|
||||||
|
@ -85,6 +86,11 @@ class BondFan(BondEntity, FanEntity):
|
||||||
return 0
|
return 0
|
||||||
return ranged_value_to_percentage(self._speed_range, self._speed)
|
return ranged_value_to_percentage(self._speed_range, self._speed)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def speed_count(self) -> Optional[int]:
|
||||||
|
"""Return the number of speeds the fan supports."""
|
||||||
|
return int_states_in_range(self._speed_range)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_direction(self) -> Optional[str]:
|
def current_direction(self) -> Optional[str]:
|
||||||
"""Return fan rotation direction."""
|
"""Return fan rotation direction."""
|
||||||
|
|
|
@ -13,6 +13,7 @@ from pycomfoconnect import (
|
||||||
from homeassistant.components.fan import SUPPORT_SET_SPEED, FanEntity
|
from homeassistant.components.fan import SUPPORT_SET_SPEED, FanEntity
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
from homeassistant.util.percentage import (
|
from homeassistant.util.percentage import (
|
||||||
|
int_states_in_range,
|
||||||
percentage_to_ranged_value,
|
percentage_to_ranged_value,
|
||||||
ranged_value_to_percentage,
|
ranged_value_to_percentage,
|
||||||
)
|
)
|
||||||
|
@ -101,6 +102,11 @@ class ComfoConnectFan(FanEntity):
|
||||||
return None
|
return None
|
||||||
return ranged_value_to_percentage(SPEED_RANGE, speed)
|
return ranged_value_to_percentage(SPEED_RANGE, speed)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def speed_count(self) -> int:
|
||||||
|
"""Return the number of speeds the fan supports."""
|
||||||
|
return int_states_in_range(SPEED_RANGE)
|
||||||
|
|
||||||
def turn_on(
|
def turn_on(
|
||||||
self, speed: str = None, percentage=None, preset_mode=None, **kwargs
|
self, speed: str = None, percentage=None, preset_mode=None, **kwargs
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
|
@ -215,6 +215,11 @@ class DemoPercentageFan(BaseDemoFan, FanEntity):
|
||||||
"""Return the current speed."""
|
"""Return the current speed."""
|
||||||
return self._percentage
|
return self._percentage
|
||||||
|
|
||||||
|
@property
|
||||||
|
def speed_count(self) -> Optional[float]:
|
||||||
|
"""Return the number of speeds the fan supports."""
|
||||||
|
return 3
|
||||||
|
|
||||||
def set_percentage(self, percentage: int) -> None:
|
def set_percentage(self, percentage: int) -> None:
|
||||||
"""Set the speed of the fan, as a percentage."""
|
"""Set the speed of the fan, as a percentage."""
|
||||||
self._percentage = percentage
|
self._percentage = percentage
|
||||||
|
@ -270,6 +275,11 @@ class AsyncDemoPercentageFan(BaseDemoFan, FanEntity):
|
||||||
"""Return the current speed."""
|
"""Return the current speed."""
|
||||||
return self._percentage
|
return self._percentage
|
||||||
|
|
||||||
|
@property
|
||||||
|
def speed_count(self) -> Optional[float]:
|
||||||
|
"""Return the number of speeds the fan supports."""
|
||||||
|
return 3
|
||||||
|
|
||||||
async def async_set_percentage(self, percentage: int) -> None:
|
async def async_set_percentage(self, percentage: int) -> None:
|
||||||
"""Set the speed of the fan, as a percentage."""
|
"""Set the speed of the fan, as a percentage."""
|
||||||
self._percentage = percentage
|
self._percentage = percentage
|
||||||
|
|
|
@ -13,6 +13,7 @@ import voluptuous as vol
|
||||||
from homeassistant.components.fan import SUPPORT_OSCILLATE, SUPPORT_SET_SPEED, FanEntity
|
from homeassistant.components.fan import SUPPORT_OSCILLATE, SUPPORT_SET_SPEED, FanEntity
|
||||||
from homeassistant.helpers import config_validation as cv, entity_platform
|
from homeassistant.helpers import config_validation as cv, entity_platform
|
||||||
from homeassistant.util.percentage import (
|
from homeassistant.util.percentage import (
|
||||||
|
int_states_in_range,
|
||||||
percentage_to_ranged_value,
|
percentage_to_ranged_value,
|
||||||
ranged_value_to_percentage,
|
ranged_value_to_percentage,
|
||||||
)
|
)
|
||||||
|
@ -154,6 +155,11 @@ class DysonFanEntity(DysonEntity, FanEntity):
|
||||||
return None
|
return None
|
||||||
return ranged_value_to_percentage(SPEED_RANGE, int(self._device.state.speed))
|
return ranged_value_to_percentage(SPEED_RANGE, int(self._device.state.speed))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def speed_count(self) -> Optional[int]:
|
||||||
|
"""Return the number of speeds the fan supports."""
|
||||||
|
return int_states_in_range(SPEED_RANGE)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def preset_modes(self):
|
def preset_modes(self):
|
||||||
"""Return the available preset modes."""
|
"""Return the available preset modes."""
|
||||||
|
|
|
@ -119,6 +119,11 @@ class EsphomeFan(EsphomeEntity, FanEntity):
|
||||||
ORDERED_NAMED_FAN_SPEEDS, self._state.speed
|
ORDERED_NAMED_FAN_SPEEDS, self._state.speed
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def speed_count(self) -> Optional[int]:
|
||||||
|
"""Return the number of speeds the fan supports."""
|
||||||
|
return len(ORDERED_NAMED_FAN_SPEEDS)
|
||||||
|
|
||||||
@esphome_state_property
|
@esphome_state_property
|
||||||
def oscillating(self) -> None:
|
def oscillating(self) -> None:
|
||||||
"""Return the oscillation state."""
|
"""Return the oscillation state."""
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import functools as ft
|
import functools as ft
|
||||||
import logging
|
import logging
|
||||||
|
import math
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
@ -23,6 +24,8 @@ from homeassistant.loader import bind_hass
|
||||||
from homeassistant.util.percentage import (
|
from homeassistant.util.percentage import (
|
||||||
ordered_list_item_to_percentage,
|
ordered_list_item_to_percentage,
|
||||||
percentage_to_ordered_list_item,
|
percentage_to_ordered_list_item,
|
||||||
|
percentage_to_ranged_value,
|
||||||
|
ranged_value_to_percentage,
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
@ -39,6 +42,8 @@ SUPPORT_DIRECTION = 4
|
||||||
SUPPORT_PRESET_MODE = 8
|
SUPPORT_PRESET_MODE = 8
|
||||||
|
|
||||||
SERVICE_SET_SPEED = "set_speed"
|
SERVICE_SET_SPEED = "set_speed"
|
||||||
|
SERVICE_INCREASE_SPEED = "increase_speed"
|
||||||
|
SERVICE_DECREASE_SPEED = "decrease_speed"
|
||||||
SERVICE_OSCILLATE = "oscillate"
|
SERVICE_OSCILLATE = "oscillate"
|
||||||
SERVICE_SET_DIRECTION = "set_direction"
|
SERVICE_SET_DIRECTION = "set_direction"
|
||||||
SERVICE_SET_PERCENTAGE = "set_percentage"
|
SERVICE_SET_PERCENTAGE = "set_percentage"
|
||||||
|
@ -54,6 +59,7 @@ DIRECTION_REVERSE = "reverse"
|
||||||
|
|
||||||
ATTR_SPEED = "speed"
|
ATTR_SPEED = "speed"
|
||||||
ATTR_PERCENTAGE = "percentage"
|
ATTR_PERCENTAGE = "percentage"
|
||||||
|
ATTR_PERCENTAGE_STEP = "percentage_step"
|
||||||
ATTR_SPEED_LIST = "speed_list"
|
ATTR_SPEED_LIST = "speed_list"
|
||||||
ATTR_OSCILLATING = "oscillating"
|
ATTR_OSCILLATING = "oscillating"
|
||||||
ATTR_DIRECTION = "direction"
|
ATTR_DIRECTION = "direction"
|
||||||
|
@ -142,6 +148,26 @@ async def async_setup(hass, config: dict):
|
||||||
"async_set_speed_deprecated",
|
"async_set_speed_deprecated",
|
||||||
[SUPPORT_SET_SPEED],
|
[SUPPORT_SET_SPEED],
|
||||||
)
|
)
|
||||||
|
component.async_register_entity_service(
|
||||||
|
SERVICE_INCREASE_SPEED,
|
||||||
|
{
|
||||||
|
vol.Optional(ATTR_PERCENTAGE_STEP): vol.All(
|
||||||
|
vol.Coerce(int), vol.Range(min=0, max=100)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
"async_increase_speed",
|
||||||
|
[SUPPORT_SET_SPEED],
|
||||||
|
)
|
||||||
|
component.async_register_entity_service(
|
||||||
|
SERVICE_DECREASE_SPEED,
|
||||||
|
{
|
||||||
|
vol.Optional(ATTR_PERCENTAGE_STEP): vol.All(
|
||||||
|
vol.Coerce(int), vol.Range(min=0, max=100)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
"async_decrease_speed",
|
||||||
|
[SUPPORT_SET_SPEED],
|
||||||
|
)
|
||||||
component.async_register_entity_service(
|
component.async_register_entity_service(
|
||||||
SERVICE_OSCILLATE,
|
SERVICE_OSCILLATE,
|
||||||
{vol.Required(ATTR_OSCILLATING): cv.boolean},
|
{vol.Required(ATTR_OSCILLATING): cv.boolean},
|
||||||
|
@ -246,6 +272,33 @@ class FanEntity(ToggleEntity):
|
||||||
else:
|
else:
|
||||||
await self.async_set_speed(self.percentage_to_speed(percentage))
|
await self.async_set_speed(self.percentage_to_speed(percentage))
|
||||||
|
|
||||||
|
async def async_increase_speed(self, percentage_step=None) -> None:
|
||||||
|
"""Increase the speed of the fan."""
|
||||||
|
await self._async_adjust_speed(1, percentage_step)
|
||||||
|
|
||||||
|
async def async_decrease_speed(self, percentage_step=None) -> None:
|
||||||
|
"""Decrease the speed of the fan."""
|
||||||
|
await self._async_adjust_speed(-1, percentage_step)
|
||||||
|
|
||||||
|
async def _async_adjust_speed(self, modifier, percentage_step) -> None:
|
||||||
|
"""Increase or decrease the speed of the fan."""
|
||||||
|
current_percentage = self.percentage or 0
|
||||||
|
|
||||||
|
if percentage_step is not None:
|
||||||
|
new_percentage = current_percentage + (percentage_step * modifier)
|
||||||
|
else:
|
||||||
|
speed_range = (1, self.speed_count)
|
||||||
|
speed_index = math.ceil(
|
||||||
|
percentage_to_ranged_value(speed_range, current_percentage)
|
||||||
|
)
|
||||||
|
new_percentage = ranged_value_to_percentage(
|
||||||
|
speed_range, speed_index + modifier
|
||||||
|
)
|
||||||
|
|
||||||
|
new_percentage = max(0, min(100, new_percentage))
|
||||||
|
|
||||||
|
await self.async_set_percentage(new_percentage)
|
||||||
|
|
||||||
@_fan_native
|
@_fan_native
|
||||||
def set_preset_mode(self, preset_mode: str) -> None:
|
def set_preset_mode(self, preset_mode: str) -> None:
|
||||||
"""Set new preset mode."""
|
"""Set new preset mode."""
|
||||||
|
@ -408,6 +461,19 @@ class FanEntity(ToggleEntity):
|
||||||
return self.speed_to_percentage(self.speed)
|
return self.speed_to_percentage(self.speed)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def speed_count(self) -> Optional[int]:
|
||||||
|
"""Return the number of speeds the fan supports."""
|
||||||
|
speed_list = speed_list_without_preset_modes(self.speed_list)
|
||||||
|
if speed_list:
|
||||||
|
return len(speed_list)
|
||||||
|
return 100
|
||||||
|
|
||||||
|
@property
|
||||||
|
def percentage_step(self) -> Optional[float]:
|
||||||
|
"""Return the step size for percentage."""
|
||||||
|
return 100 / self.speed_count
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def speed_list(self) -> list:
|
def speed_list(self) -> list:
|
||||||
"""Get the list of available speeds."""
|
"""Get the list of available speeds."""
|
||||||
|
@ -531,6 +597,7 @@ 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
|
data[ATTR_PERCENTAGE] = self.percentage
|
||||||
|
data[ATTR_PERCENTAGE_STEP] = self.percentage_step
|
||||||
|
|
||||||
if (
|
if (
|
||||||
supported_features & SUPPORT_PRESET_MODE
|
supported_features & SUPPORT_PRESET_MODE
|
||||||
|
|
|
@ -100,3 +100,41 @@ set_direction:
|
||||||
options:
|
options:
|
||||||
- "forward"
|
- "forward"
|
||||||
- "reverse"
|
- "reverse"
|
||||||
|
|
||||||
|
increase_speed:
|
||||||
|
description: Increase the speed of the fan by one speed or a percentage_step.
|
||||||
|
fields:
|
||||||
|
entity_id:
|
||||||
|
description: Name(s) of the entities to increase speed
|
||||||
|
example: "fan.living_room"
|
||||||
|
percentage_step:
|
||||||
|
advanced: true
|
||||||
|
required: false
|
||||||
|
description: Increase speed by a percentage. Should be between 0..100. [optional]
|
||||||
|
example: 50
|
||||||
|
selector:
|
||||||
|
number:
|
||||||
|
min: 0
|
||||||
|
max: 100
|
||||||
|
step: 1
|
||||||
|
unit_of_measurement: "%"
|
||||||
|
mode: slider
|
||||||
|
|
||||||
|
decrease_speed:
|
||||||
|
description: Decrease the speed of the fan by one speed or a percentage_step.
|
||||||
|
fields:
|
||||||
|
entity_id:
|
||||||
|
description: Name(s) of the entities to decrease speed
|
||||||
|
example: "fan.living_room"
|
||||||
|
percentage_step:
|
||||||
|
advanced: true
|
||||||
|
required: false
|
||||||
|
description: Decrease speed by a percentage. Should be between 0..100. [optional]
|
||||||
|
example: 50
|
||||||
|
selector:
|
||||||
|
number:
|
||||||
|
min: 0
|
||||||
|
max: 100
|
||||||
|
step: 1
|
||||||
|
unit_of_measurement: "%"
|
||||||
|
mode: slider
|
||||||
|
|
|
@ -2,12 +2,13 @@
|
||||||
import math
|
import math
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
|
|
||||||
from pyisy.constants import ISY_VALUE_UNKNOWN
|
from pyisy.constants import ISY_VALUE_UNKNOWN, PROTO_INSTEON
|
||||||
|
|
||||||
from homeassistant.components.fan import DOMAIN as FAN, SUPPORT_SET_SPEED, FanEntity
|
from homeassistant.components.fan import DOMAIN as FAN, SUPPORT_SET_SPEED, FanEntity
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.helpers.typing import HomeAssistantType
|
from homeassistant.helpers.typing import HomeAssistantType
|
||||||
from homeassistant.util.percentage import (
|
from homeassistant.util.percentage import (
|
||||||
|
int_states_in_range,
|
||||||
percentage_to_ranged_value,
|
percentage_to_ranged_value,
|
||||||
ranged_value_to_percentage,
|
ranged_value_to_percentage,
|
||||||
)
|
)
|
||||||
|
@ -48,6 +49,13 @@ class ISYFanEntity(ISYNodeEntity, FanEntity):
|
||||||
return None
|
return None
|
||||||
return ranged_value_to_percentage(SPEED_RANGE, self._node.status)
|
return ranged_value_to_percentage(SPEED_RANGE, self._node.status)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def speed_count(self) -> int:
|
||||||
|
"""Return the number of speeds the fan supports."""
|
||||||
|
if self._node.protocol == PROTO_INSTEON:
|
||||||
|
return 3
|
||||||
|
return int_states_in_range(SPEED_RANGE)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> bool:
|
def is_on(self) -> bool:
|
||||||
"""Get if the fan is on."""
|
"""Get if the fan is on."""
|
||||||
|
@ -95,6 +103,13 @@ class ISYFanProgramEntity(ISYProgramEntity, FanEntity):
|
||||||
return None
|
return None
|
||||||
return ranged_value_to_percentage(SPEED_RANGE, self._node.status)
|
return ranged_value_to_percentage(SPEED_RANGE, self._node.status)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def speed_count(self) -> int:
|
||||||
|
"""Return the number of speeds the fan supports."""
|
||||||
|
if self._node.protocol == PROTO_INSTEON:
|
||||||
|
return 3
|
||||||
|
return int_states_in_range(SPEED_RANGE)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> bool:
|
def is_on(self) -> bool:
|
||||||
"""Get if the fan is on."""
|
"""Get if the fan is on."""
|
||||||
|
|
|
@ -7,6 +7,7 @@ from xknx.devices.fan import FanSpeedMode
|
||||||
|
|
||||||
from homeassistant.components.fan import SUPPORT_OSCILLATE, SUPPORT_SET_SPEED, FanEntity
|
from homeassistant.components.fan import SUPPORT_OSCILLATE, SUPPORT_SET_SPEED, FanEntity
|
||||||
from homeassistant.util.percentage import (
|
from homeassistant.util.percentage import (
|
||||||
|
int_states_in_range,
|
||||||
percentage_to_ranged_value,
|
percentage_to_ranged_value,
|
||||||
ranged_value_to_percentage,
|
ranged_value_to_percentage,
|
||||||
)
|
)
|
||||||
|
@ -68,6 +69,13 @@ class KNXFan(KnxEntity, FanEntity):
|
||||||
)
|
)
|
||||||
return self._device.current_speed
|
return self._device.current_speed
|
||||||
|
|
||||||
|
@property
|
||||||
|
def speed_count(self) -> Optional[int]:
|
||||||
|
"""Return the number of speeds the fan supports."""
|
||||||
|
if self._step_range is None:
|
||||||
|
return super().speed_count
|
||||||
|
return int_states_in_range(self._step_range)
|
||||||
|
|
||||||
async def async_turn_on(
|
async def async_turn_on(
|
||||||
self,
|
self,
|
||||||
speed: Optional[str] = None,
|
speed: Optional[str] = None,
|
||||||
|
|
|
@ -48,6 +48,11 @@ class LutronCasetaFan(LutronCasetaDevice, FanEntity):
|
||||||
ORDERED_NAMED_FAN_SPEEDS, self._device["fan_speed"]
|
ORDERED_NAMED_FAN_SPEEDS, self._device["fan_speed"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def speed_count(self) -> int:
|
||||||
|
"""Return the number of speeds the fan supports."""
|
||||||
|
return len(ORDERED_NAMED_FAN_SPEEDS)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self) -> int:
|
def supported_features(self) -> int:
|
||||||
"""Flag supported features. Speed Only."""
|
"""Flag supported features. Speed Only."""
|
||||||
|
|
|
@ -9,6 +9,7 @@ from homeassistant.components.fan import (
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
from homeassistant.util.percentage import (
|
from homeassistant.util.percentage import (
|
||||||
|
int_states_in_range,
|
||||||
percentage_to_ranged_value,
|
percentage_to_ranged_value,
|
||||||
ranged_value_to_percentage,
|
ranged_value_to_percentage,
|
||||||
)
|
)
|
||||||
|
@ -72,6 +73,11 @@ class ZwaveFan(ZWaveDeviceEntity, FanEntity):
|
||||||
"""
|
"""
|
||||||
return ranged_value_to_percentage(SPEED_RANGE, self.values.primary.value)
|
return ranged_value_to_percentage(SPEED_RANGE, self.values.primary.value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def speed_count(self) -> int:
|
||||||
|
"""Return the number of speeds the fan supports."""
|
||||||
|
return int_states_in_range(SPEED_RANGE)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self):
|
||||||
"""Flag supported features."""
|
"""Flag supported features."""
|
||||||
|
|
|
@ -6,6 +6,7 @@ from pysmartthings import Capability
|
||||||
|
|
||||||
from homeassistant.components.fan import SUPPORT_SET_SPEED, FanEntity
|
from homeassistant.components.fan import SUPPORT_SET_SPEED, FanEntity
|
||||||
from homeassistant.util.percentage import (
|
from homeassistant.util.percentage import (
|
||||||
|
int_states_in_range,
|
||||||
percentage_to_ranged_value,
|
percentage_to_ranged_value,
|
||||||
ranged_value_to_percentage,
|
ranged_value_to_percentage,
|
||||||
)
|
)
|
||||||
|
@ -79,6 +80,11 @@ class SmartThingsFan(SmartThingsEntity, FanEntity):
|
||||||
"""Return the current speed percentage."""
|
"""Return the current speed percentage."""
|
||||||
return ranged_value_to_percentage(SPEED_RANGE, self._device.status.fan_speed)
|
return ranged_value_to_percentage(SPEED_RANGE, self._device.status.fan_speed)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def speed_count(self) -> Optional[int]:
|
||||||
|
"""Return the number of speeds the fan supports."""
|
||||||
|
return int_states_in_range(SPEED_RANGE)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self) -> int:
|
def supported_features(self) -> int:
|
||||||
"""Flag supported features."""
|
"""Flag supported features."""
|
||||||
|
|
|
@ -46,6 +46,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
CONF_FANS = "fans"
|
CONF_FANS = "fans"
|
||||||
CONF_SPEED_LIST = "speeds"
|
CONF_SPEED_LIST = "speeds"
|
||||||
|
CONF_SPEED_COUNT = "speed_count"
|
||||||
CONF_PRESET_MODES = "preset_modes"
|
CONF_PRESET_MODES = "preset_modes"
|
||||||
CONF_SPEED_TEMPLATE = "speed_template"
|
CONF_SPEED_TEMPLATE = "speed_template"
|
||||||
CONF_PERCENTAGE_TEMPLATE = "percentage_template"
|
CONF_PERCENTAGE_TEMPLATE = "percentage_template"
|
||||||
|
@ -86,6 +87,7 @@ FAN_SCHEMA = vol.All(
|
||||||
vol.Optional(CONF_SET_PRESET_MODE_ACTION): cv.SCRIPT_SCHEMA,
|
vol.Optional(CONF_SET_PRESET_MODE_ACTION): cv.SCRIPT_SCHEMA,
|
||||||
vol.Optional(CONF_SET_OSCILLATING_ACTION): cv.SCRIPT_SCHEMA,
|
vol.Optional(CONF_SET_OSCILLATING_ACTION): cv.SCRIPT_SCHEMA,
|
||||||
vol.Optional(CONF_SET_DIRECTION_ACTION): cv.SCRIPT_SCHEMA,
|
vol.Optional(CONF_SET_DIRECTION_ACTION): cv.SCRIPT_SCHEMA,
|
||||||
|
vol.Optional(CONF_SPEED_COUNT): vol.Coerce(int),
|
||||||
vol.Optional(
|
vol.Optional(
|
||||||
CONF_SPEED_LIST,
|
CONF_SPEED_LIST,
|
||||||
default=[SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH],
|
default=[SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH],
|
||||||
|
@ -126,6 +128,7 @@ async def _async_create_entities(hass, config):
|
||||||
set_direction_action = device_config.get(CONF_SET_DIRECTION_ACTION)
|
set_direction_action = device_config.get(CONF_SET_DIRECTION_ACTION)
|
||||||
|
|
||||||
speed_list = device_config[CONF_SPEED_LIST]
|
speed_list = device_config[CONF_SPEED_LIST]
|
||||||
|
speed_count = device_config.get(CONF_SPEED_COUNT)
|
||||||
preset_modes = device_config.get(CONF_PRESET_MODES)
|
preset_modes = device_config.get(CONF_PRESET_MODES)
|
||||||
unique_id = device_config.get(CONF_UNIQUE_ID)
|
unique_id = device_config.get(CONF_UNIQUE_ID)
|
||||||
|
|
||||||
|
@ -148,6 +151,7 @@ async def _async_create_entities(hass, config):
|
||||||
set_preset_mode_action,
|
set_preset_mode_action,
|
||||||
set_oscillating_action,
|
set_oscillating_action,
|
||||||
set_direction_action,
|
set_direction_action,
|
||||||
|
speed_count,
|
||||||
speed_list,
|
speed_list,
|
||||||
preset_modes,
|
preset_modes,
|
||||||
unique_id,
|
unique_id,
|
||||||
|
@ -185,6 +189,7 @@ class TemplateFan(TemplateEntity, FanEntity):
|
||||||
set_preset_mode_action,
|
set_preset_mode_action,
|
||||||
set_oscillating_action,
|
set_oscillating_action,
|
||||||
set_direction_action,
|
set_direction_action,
|
||||||
|
speed_count,
|
||||||
speed_list,
|
speed_list,
|
||||||
preset_modes,
|
preset_modes,
|
||||||
unique_id,
|
unique_id,
|
||||||
|
@ -260,6 +265,9 @@ class TemplateFan(TemplateEntity, FanEntity):
|
||||||
|
|
||||||
self._unique_id = unique_id
|
self._unique_id = unique_id
|
||||||
|
|
||||||
|
# Number of valid speeds
|
||||||
|
self._speed_count = speed_count
|
||||||
|
|
||||||
# List of valid speeds
|
# List of valid speeds
|
||||||
self._speed_list = speed_list
|
self._speed_list = speed_list
|
||||||
|
|
||||||
|
@ -281,6 +289,11 @@ class TemplateFan(TemplateEntity, FanEntity):
|
||||||
"""Flag supported features."""
|
"""Flag supported features."""
|
||||||
return self._supported_features
|
return self._supported_features
|
||||||
|
|
||||||
|
@property
|
||||||
|
def speed_count(self) -> int:
|
||||||
|
"""Return the number of speeds the fan supports."""
|
||||||
|
return self._speed_count or super().speed_count
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def speed_list(self) -> list:
|
def speed_list(self) -> list:
|
||||||
"""Get the list of available speeds."""
|
"""Get the list of available speeds."""
|
||||||
|
|
|
@ -102,6 +102,13 @@ class TuyaFanDevice(TuyaDevice, FanEntity):
|
||||||
"""Oscillate the fan."""
|
"""Oscillate the fan."""
|
||||||
self._tuya.oscillate(oscillating)
|
self._tuya.oscillate(oscillating)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def speed_count(self) -> Optional[int]:
|
||||||
|
"""Return the number of speeds the fan supports."""
|
||||||
|
if self.speeds is None:
|
||||||
|
return super().speed_count
|
||||||
|
return len(self.speeds)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def oscillating(self):
|
def oscillating(self):
|
||||||
"""Return current oscillating status."""
|
"""Return current oscillating status."""
|
||||||
|
|
|
@ -6,6 +6,7 @@ from homeassistant.components.fan import SUPPORT_SET_SPEED, FanEntity
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
from homeassistant.util.percentage import (
|
from homeassistant.util.percentage import (
|
||||||
|
int_states_in_range,
|
||||||
percentage_to_ranged_value,
|
percentage_to_ranged_value,
|
||||||
ranged_value_to_percentage,
|
ranged_value_to_percentage,
|
||||||
)
|
)
|
||||||
|
@ -77,6 +78,11 @@ class VeSyncFanHA(VeSyncDevice, FanEntity):
|
||||||
return ranged_value_to_percentage(SPEED_RANGE, current_level)
|
return ranged_value_to_percentage(SPEED_RANGE, current_level)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def speed_count(self) -> int:
|
||||||
|
"""Return the number of speeds the fan supports."""
|
||||||
|
return int_states_in_range(SPEED_RANGE)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def preset_modes(self):
|
def preset_modes(self):
|
||||||
"""Get the list of available preset modes."""
|
"""Get the list of available preset modes."""
|
||||||
|
|
|
@ -10,6 +10,7 @@ from homeassistant.components.fan import SUPPORT_SET_SPEED, FanEntity
|
||||||
from homeassistant.helpers import entity_platform
|
from homeassistant.helpers import entity_platform
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
from homeassistant.util.percentage import (
|
from homeassistant.util.percentage import (
|
||||||
|
int_states_in_range,
|
||||||
percentage_to_ranged_value,
|
percentage_to_ranged_value,
|
||||||
ranged_value_to_percentage,
|
ranged_value_to_percentage,
|
||||||
)
|
)
|
||||||
|
@ -130,6 +131,11 @@ class WemoHumidifier(WemoSubscriptionEntity, FanEntity):
|
||||||
"""Return the current speed percentage."""
|
"""Return the current speed percentage."""
|
||||||
return ranged_value_to_percentage(SPEED_RANGE, self._fan_mode)
|
return ranged_value_to_percentage(SPEED_RANGE, self._fan_mode)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def speed_count(self) -> int:
|
||||||
|
"""Return the number of speeds the fan supports."""
|
||||||
|
return int_states_in_range(SPEED_RANGE)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self) -> int:
|
def supported_features(self) -> int:
|
||||||
"""Flag supported features."""
|
"""Flag supported features."""
|
||||||
|
|
|
@ -87,6 +87,11 @@ class WiLightFan(WiLightDevice, FanEntity):
|
||||||
return None
|
return None
|
||||||
return ordered_list_item_to_percentage(ORDERED_NAMED_FAN_SPEEDS, wl_speed)
|
return ordered_list_item_to_percentage(ORDERED_NAMED_FAN_SPEEDS, wl_speed)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def speed_count(self) -> int:
|
||||||
|
"""Return the number of speeds the fan supports."""
|
||||||
|
return len(ORDERED_NAMED_FAN_SPEEDS)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_direction(self) -> str:
|
def current_direction(self) -> str:
|
||||||
"""Return the current direction of the fan."""
|
"""Return the current direction of the fan."""
|
||||||
|
|
|
@ -5,6 +5,7 @@ from homeassistant.components.fan import DOMAIN, SUPPORT_SET_SPEED, FanEntity
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
from homeassistant.util.percentage import (
|
from homeassistant.util.percentage import (
|
||||||
|
int_states_in_range,
|
||||||
percentage_to_ranged_value,
|
percentage_to_ranged_value,
|
||||||
ranged_value_to_percentage,
|
ranged_value_to_percentage,
|
||||||
)
|
)
|
||||||
|
@ -68,6 +69,11 @@ class ZwaveFan(ZWaveDeviceEntity, FanEntity):
|
||||||
"""Return the current speed percentage."""
|
"""Return the current speed percentage."""
|
||||||
return ranged_value_to_percentage(SPEED_RANGE, self._state)
|
return ranged_value_to_percentage(SPEED_RANGE, self._state)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def speed_count(self) -> int:
|
||||||
|
"""Return the number of speeds the fan supports."""
|
||||||
|
return int_states_in_range(SPEED_RANGE)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self):
|
||||||
"""Flag supported features."""
|
"""Flag supported features."""
|
||||||
|
|
|
@ -13,6 +13,7 @@ from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
from homeassistant.util.percentage import (
|
from homeassistant.util.percentage import (
|
||||||
|
int_states_in_range,
|
||||||
percentage_to_ranged_value,
|
percentage_to_ranged_value,
|
||||||
ranged_value_to_percentage,
|
ranged_value_to_percentage,
|
||||||
)
|
)
|
||||||
|
@ -96,6 +97,11 @@ class ZwaveFan(ZWaveBaseEntity, FanEntity):
|
||||||
return None
|
return None
|
||||||
return ranged_value_to_percentage(SPEED_RANGE, self.info.primary_value.value)
|
return ranged_value_to_percentage(SPEED_RANGE, self.info.primary_value.value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def speed_count(self) -> Optional[int]:
|
||||||
|
"""Return the number of speeds the fan supports."""
|
||||||
|
return int_states_in_range(SPEED_RANGE)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self) -> int:
|
def supported_features(self) -> int:
|
||||||
"""Flag supported features."""
|
"""Flag supported features."""
|
||||||
|
|
|
@ -67,7 +67,7 @@ def ranged_value_to_percentage(
|
||||||
(1,255), 127: 50
|
(1,255), 127: 50
|
||||||
(1,255), 10: 4
|
(1,255), 10: 4
|
||||||
"""
|
"""
|
||||||
return int((value * 100) // (low_high_range[1] - low_high_range[0] + 1))
|
return int((value * 100) // states_in_range(low_high_range))
|
||||||
|
|
||||||
|
|
||||||
def percentage_to_ranged_value(
|
def percentage_to_ranged_value(
|
||||||
|
@ -84,4 +84,14 @@ def percentage_to_ranged_value(
|
||||||
(1,255), 50: 127.5
|
(1,255), 50: 127.5
|
||||||
(1,255), 4: 10.2
|
(1,255), 4: 10.2
|
||||||
"""
|
"""
|
||||||
return (low_high_range[1] - low_high_range[0] + 1) * percentage / 100
|
return states_in_range(low_high_range) * percentage / 100
|
||||||
|
|
||||||
|
|
||||||
|
def states_in_range(low_high_range: Tuple[float, float]) -> float:
|
||||||
|
"""Given a range of low and high values return how many states exist."""
|
||||||
|
return low_high_range[1] - low_high_range[0] + 1
|
||||||
|
|
||||||
|
|
||||||
|
def int_states_in_range(low_high_range: Tuple[float, float]) -> int:
|
||||||
|
"""Given a range of low and high values return how many integer states exist."""
|
||||||
|
return int(states_in_range(low_high_range))
|
||||||
|
|
|
@ -27,6 +27,7 @@ LIMITED_AND_FULL_FAN_ENTITY_IDS = FULL_FAN_ENTITY_IDS + [
|
||||||
FANS_WITH_PRESET_MODES = FULL_FAN_ENTITY_IDS + [
|
FANS_WITH_PRESET_MODES = FULL_FAN_ENTITY_IDS + [
|
||||||
"fan.percentage_limited_fan",
|
"fan.percentage_limited_fan",
|
||||||
]
|
]
|
||||||
|
PERCENTAGE_MODEL_FANS = ["fan.percentage_full_fan", "fan.percentage_limited_fan"]
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
|
@ -397,6 +398,128 @@ async def test_set_percentage(hass, fan_entity_id):
|
||||||
assert state.attributes[fan.ATTR_PERCENTAGE] == 33
|
assert state.attributes[fan.ATTR_PERCENTAGE] == 33
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("fan_entity_id", LIMITED_AND_FULL_FAN_ENTITY_IDS)
|
||||||
|
async def test_increase_decrease_speed(hass, fan_entity_id):
|
||||||
|
"""Test increasing and decreasing the percentage speed of the device."""
|
||||||
|
state = hass.states.get(fan_entity_id)
|
||||||
|
assert state.state == STATE_OFF
|
||||||
|
assert state.attributes[fan.ATTR_PERCENTAGE_STEP] == 100 / 3
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
fan.DOMAIN,
|
||||||
|
fan.SERVICE_INCREASE_SPEED,
|
||||||
|
{ATTR_ENTITY_ID: fan_entity_id},
|
||||||
|
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
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
fan.DOMAIN,
|
||||||
|
fan.SERVICE_INCREASE_SPEED,
|
||||||
|
{ATTR_ENTITY_ID: fan_entity_id},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
state = hass.states.get(fan_entity_id)
|
||||||
|
assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_MEDIUM
|
||||||
|
assert state.attributes[fan.ATTR_PERCENTAGE] == 66
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
fan.DOMAIN,
|
||||||
|
fan.SERVICE_INCREASE_SPEED,
|
||||||
|
{ATTR_ENTITY_ID: fan_entity_id},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
state = hass.states.get(fan_entity_id)
|
||||||
|
assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_HIGH
|
||||||
|
assert state.attributes[fan.ATTR_PERCENTAGE] == 100
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
fan.DOMAIN,
|
||||||
|
fan.SERVICE_INCREASE_SPEED,
|
||||||
|
{ATTR_ENTITY_ID: fan_entity_id},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
state = hass.states.get(fan_entity_id)
|
||||||
|
assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_HIGH
|
||||||
|
assert state.attributes[fan.ATTR_PERCENTAGE] == 100
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
fan.DOMAIN,
|
||||||
|
fan.SERVICE_DECREASE_SPEED,
|
||||||
|
{ATTR_ENTITY_ID: fan_entity_id},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
state = hass.states.get(fan_entity_id)
|
||||||
|
assert state.attributes[fan.ATTR_PERCENTAGE] == 66
|
||||||
|
assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_MEDIUM
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
fan.DOMAIN,
|
||||||
|
fan.SERVICE_DECREASE_SPEED,
|
||||||
|
{ATTR_ENTITY_ID: fan_entity_id},
|
||||||
|
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
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
fan.DOMAIN,
|
||||||
|
fan.SERVICE_DECREASE_SPEED,
|
||||||
|
{ATTR_ENTITY_ID: fan_entity_id},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
state = hass.states.get(fan_entity_id)
|
||||||
|
assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_OFF
|
||||||
|
assert state.attributes[fan.ATTR_PERCENTAGE] == 0
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
fan.DOMAIN,
|
||||||
|
fan.SERVICE_DECREASE_SPEED,
|
||||||
|
{ATTR_ENTITY_ID: fan_entity_id},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
state = hass.states.get(fan_entity_id)
|
||||||
|
assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_OFF
|
||||||
|
assert state.attributes[fan.ATTR_PERCENTAGE] == 0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("fan_entity_id", PERCENTAGE_MODEL_FANS)
|
||||||
|
async def test_increase_decrease_speed_with_percentage_step(hass, fan_entity_id):
|
||||||
|
"""Test increasing speed with a percentage step."""
|
||||||
|
await hass.services.async_call(
|
||||||
|
fan.DOMAIN,
|
||||||
|
fan.SERVICE_INCREASE_SPEED,
|
||||||
|
{ATTR_ENTITY_ID: fan_entity_id, fan.ATTR_PERCENTAGE_STEP: 25},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
state = hass.states.get(fan_entity_id)
|
||||||
|
assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_LOW
|
||||||
|
assert state.attributes[fan.ATTR_PERCENTAGE] == 25
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
fan.DOMAIN,
|
||||||
|
fan.SERVICE_INCREASE_SPEED,
|
||||||
|
{ATTR_ENTITY_ID: fan_entity_id, fan.ATTR_PERCENTAGE_STEP: 25},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
state = hass.states.get(fan_entity_id)
|
||||||
|
assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_MEDIUM
|
||||||
|
assert state.attributes[fan.ATTR_PERCENTAGE] == 50
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
fan.DOMAIN,
|
||||||
|
fan.SERVICE_INCREASE_SPEED,
|
||||||
|
{ATTR_ENTITY_ID: fan_entity_id, fan.ATTR_PERCENTAGE_STEP: 25},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
state = hass.states.get(fan_entity_id)
|
||||||
|
assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_HIGH
|
||||||
|
assert state.attributes[fan.ATTR_PERCENTAGE] == 75
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("fan_entity_id", FULL_FAN_ENTITY_IDS)
|
@pytest.mark.parametrize("fan_entity_id", FULL_FAN_ENTITY_IDS)
|
||||||
async def test_oscillate(hass, fan_entity_id):
|
async def test_oscillate(hass, fan_entity_id):
|
||||||
"""Test oscillating the fan."""
|
"""Test oscillating the fan."""
|
||||||
|
|
|
@ -7,9 +7,12 @@ from homeassistant.components.fan import (
|
||||||
ATTR_DIRECTION,
|
ATTR_DIRECTION,
|
||||||
ATTR_OSCILLATING,
|
ATTR_OSCILLATING,
|
||||||
ATTR_PERCENTAGE,
|
ATTR_PERCENTAGE,
|
||||||
|
ATTR_PERCENTAGE_STEP,
|
||||||
ATTR_PRESET_MODE,
|
ATTR_PRESET_MODE,
|
||||||
ATTR_SPEED,
|
ATTR_SPEED,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
|
SERVICE_DECREASE_SPEED,
|
||||||
|
SERVICE_INCREASE_SPEED,
|
||||||
SERVICE_OSCILLATE,
|
SERVICE_OSCILLATE,
|
||||||
SERVICE_SET_DIRECTION,
|
SERVICE_SET_DIRECTION,
|
||||||
SERVICE_SET_PERCENTAGE,
|
SERVICE_SET_PERCENTAGE,
|
||||||
|
@ -106,6 +109,38 @@ async def async_set_percentage(
|
||||||
await hass.services.async_call(DOMAIN, SERVICE_SET_PERCENTAGE, data, blocking=True)
|
await hass.services.async_call(DOMAIN, SERVICE_SET_PERCENTAGE, data, blocking=True)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_increase_speed(
|
||||||
|
hass, entity_id=ENTITY_MATCH_ALL, percentage_step: int = None
|
||||||
|
) -> None:
|
||||||
|
"""Increase speed for all or specified fan."""
|
||||||
|
data = {
|
||||||
|
key: value
|
||||||
|
for key, value in [
|
||||||
|
(ATTR_ENTITY_ID, entity_id),
|
||||||
|
(ATTR_PERCENTAGE_STEP, percentage_step),
|
||||||
|
]
|
||||||
|
if value is not None
|
||||||
|
}
|
||||||
|
|
||||||
|
await hass.services.async_call(DOMAIN, SERVICE_INCREASE_SPEED, data, blocking=True)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_decrease_speed(
|
||||||
|
hass, entity_id=ENTITY_MATCH_ALL, percentage_step: int = None
|
||||||
|
) -> None:
|
||||||
|
"""Decrease speed for all or specified fan."""
|
||||||
|
data = {
|
||||||
|
key: value
|
||||||
|
for key, value in [
|
||||||
|
(ATTR_ENTITY_ID, entity_id),
|
||||||
|
(ATTR_PERCENTAGE_STEP, percentage_step),
|
||||||
|
]
|
||||||
|
if value is not None
|
||||||
|
}
|
||||||
|
|
||||||
|
await hass.services.async_call(DOMAIN, SERVICE_DECREASE_SPEED, 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:
|
||||||
|
|
|
@ -19,6 +19,8 @@ def test_fanentity():
|
||||||
assert len(fan.speed_list) == 0
|
assert len(fan.speed_list) == 0
|
||||||
assert len(fan.preset_modes) == 0
|
assert len(fan.preset_modes) == 0
|
||||||
assert fan.supported_features == 0
|
assert fan.supported_features == 0
|
||||||
|
assert fan.percentage_step == 1
|
||||||
|
assert fan.speed_count == 100
|
||||||
assert fan.capability_attributes == {}
|
assert fan.capability_attributes == {}
|
||||||
# Test set_speed not required
|
# Test set_speed not required
|
||||||
with pytest.raises(NotImplementedError):
|
with pytest.raises(NotImplementedError):
|
||||||
|
@ -43,6 +45,8 @@ async def test_async_fanentity(hass):
|
||||||
assert len(fan.speed_list) == 0
|
assert len(fan.speed_list) == 0
|
||||||
assert len(fan.preset_modes) == 0
|
assert len(fan.preset_modes) == 0
|
||||||
assert fan.supported_features == 0
|
assert fan.supported_features == 0
|
||||||
|
assert fan.percentage_step == 1
|
||||||
|
assert fan.speed_count == 100
|
||||||
assert fan.capability_attributes == {}
|
assert fan.capability_attributes == {}
|
||||||
# Test set_speed not required
|
# Test set_speed not required
|
||||||
with pytest.raises(NotImplementedError):
|
with pytest.raises(NotImplementedError):
|
||||||
|
@ -57,3 +61,7 @@ async def test_async_fanentity(hass):
|
||||||
await fan.async_turn_on()
|
await fan.async_turn_on()
|
||||||
with pytest.raises(NotImplementedError):
|
with pytest.raises(NotImplementedError):
|
||||||
await fan.async_turn_off()
|
await fan.async_turn_off()
|
||||||
|
with pytest.raises(NotImplementedError):
|
||||||
|
await fan.async_increase_speed()
|
||||||
|
with pytest.raises(NotImplementedError):
|
||||||
|
await fan.async_decrease_speed()
|
||||||
|
|
|
@ -203,6 +203,7 @@ async def test_templates_with_entities(hass, calls):
|
||||||
"preset_mode_template": "{{ states('input_select.preset_mode') }}",
|
"preset_mode_template": "{{ states('input_select.preset_mode') }}",
|
||||||
"oscillating_template": "{{ states('input_select.osc') }}",
|
"oscillating_template": "{{ states('input_select.osc') }}",
|
||||||
"direction_template": "{{ states('input_select.direction') }}",
|
"direction_template": "{{ states('input_select.direction') }}",
|
||||||
|
"speed_count": "3",
|
||||||
"set_percentage": {
|
"set_percentage": {
|
||||||
"service": "script.fans_set_speed",
|
"service": "script.fans_set_speed",
|
||||||
"data_template": {"percentage": "{{ percentage }}"},
|
"data_template": {"percentage": "{{ percentage }}"},
|
||||||
|
@ -648,6 +649,46 @@ async def test_set_percentage(hass, calls):
|
||||||
_verify(hass, STATE_ON, SPEED_MEDIUM, 50, None, None, None)
|
_verify(hass, STATE_ON, SPEED_MEDIUM, 50, None, None, None)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_increase_decrease_speed(hass, calls):
|
||||||
|
"""Test set valid increase and derease speed."""
|
||||||
|
await _register_components(hass)
|
||||||
|
|
||||||
|
# Turn on fan
|
||||||
|
await common.async_turn_on(hass, _TEST_FAN)
|
||||||
|
|
||||||
|
# Set fan's percentage speed to 100
|
||||||
|
await common.async_set_percentage(hass, _TEST_FAN, 100)
|
||||||
|
|
||||||
|
# verify
|
||||||
|
assert int(float(hass.states.get(_PERCENTAGE_INPUT_NUMBER).state)) == 100
|
||||||
|
|
||||||
|
_verify(hass, STATE_ON, SPEED_HIGH, 100, None, None, None)
|
||||||
|
|
||||||
|
# Set fan's percentage speed to 66
|
||||||
|
await common.async_decrease_speed(hass, _TEST_FAN)
|
||||||
|
assert int(float(hass.states.get(_PERCENTAGE_INPUT_NUMBER).state)) == 66
|
||||||
|
|
||||||
|
_verify(hass, STATE_ON, SPEED_MEDIUM, 66, None, None, None)
|
||||||
|
|
||||||
|
# Set fan's percentage speed to 33
|
||||||
|
await common.async_decrease_speed(hass, _TEST_FAN)
|
||||||
|
assert int(float(hass.states.get(_PERCENTAGE_INPUT_NUMBER).state)) == 33
|
||||||
|
|
||||||
|
_verify(hass, STATE_ON, SPEED_LOW, 33, None, None, None)
|
||||||
|
|
||||||
|
# Set fan's percentage speed to 0
|
||||||
|
await common.async_decrease_speed(hass, _TEST_FAN)
|
||||||
|
assert int(float(hass.states.get(_PERCENTAGE_INPUT_NUMBER).state)) == 0
|
||||||
|
|
||||||
|
_verify(hass, STATE_OFF, SPEED_OFF, 0, None, None, None)
|
||||||
|
|
||||||
|
# Set fan's percentage speed to 33
|
||||||
|
await common.async_increase_speed(hass, _TEST_FAN)
|
||||||
|
assert int(float(hass.states.get(_PERCENTAGE_INPUT_NUMBER).state)) == 33
|
||||||
|
|
||||||
|
_verify(hass, STATE_ON, SPEED_LOW, 33, None, None, None)
|
||||||
|
|
||||||
|
|
||||||
async def test_set_invalid_speed_from_initial_stage(hass, calls):
|
async def test_set_invalid_speed_from_initial_stage(hass, calls):
|
||||||
"""Test set invalid speed when fan is in initial state."""
|
"""Test set invalid speed when fan is in initial state."""
|
||||||
await _register_components(hass)
|
await _register_components(hass)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue