Implement percentage step sizes for fans (#46512)
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>pull/46777/head
parent
5df46b60e8
commit
f2b303d509
|
@ -16,6 +16,7 @@ from homeassistant.config_entries import ConfigEntry
|
|||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.util.percentage import (
|
||||
int_states_in_range,
|
||||
percentage_to_ranged_value,
|
||||
ranged_value_to_percentage,
|
||||
)
|
||||
|
@ -85,6 +86,11 @@ class BondFan(BondEntity, FanEntity):
|
|||
return 0
|
||||
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
|
||||
def current_direction(self) -> Optional[str]:
|
||||
"""Return fan rotation direction."""
|
||||
|
|
|
@ -13,6 +13,7 @@ from pycomfoconnect import (
|
|||
from homeassistant.components.fan import SUPPORT_SET_SPEED, FanEntity
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.util.percentage import (
|
||||
int_states_in_range,
|
||||
percentage_to_ranged_value,
|
||||
ranged_value_to_percentage,
|
||||
)
|
||||
|
@ -101,6 +102,11 @@ class ComfoConnectFan(FanEntity):
|
|||
return None
|
||||
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(
|
||||
self, speed: str = None, percentage=None, preset_mode=None, **kwargs
|
||||
) -> None:
|
||||
|
|
|
@ -215,6 +215,11 @@ class DemoPercentageFan(BaseDemoFan, FanEntity):
|
|||
"""Return the current speed."""
|
||||
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:
|
||||
"""Set the speed of the fan, as a percentage."""
|
||||
self._percentage = percentage
|
||||
|
@ -270,6 +275,11 @@ class AsyncDemoPercentageFan(BaseDemoFan, FanEntity):
|
|||
"""Return the current speed."""
|
||||
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:
|
||||
"""Set the speed of the fan, as a 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.helpers import config_validation as cv, entity_platform
|
||||
from homeassistant.util.percentage import (
|
||||
int_states_in_range,
|
||||
percentage_to_ranged_value,
|
||||
ranged_value_to_percentage,
|
||||
)
|
||||
|
@ -154,6 +155,11 @@ class DysonFanEntity(DysonEntity, FanEntity):
|
|||
return None
|
||||
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
|
||||
def preset_modes(self):
|
||||
"""Return the available preset modes."""
|
||||
|
|
|
@ -119,6 +119,11 @@ class EsphomeFan(EsphomeEntity, FanEntity):
|
|||
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
|
||||
def oscillating(self) -> None:
|
||||
"""Return the oscillation state."""
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
from datetime import timedelta
|
||||
import functools as ft
|
||||
import logging
|
||||
import math
|
||||
from typing import List, Optional
|
||||
|
||||
import voluptuous as vol
|
||||
|
@ -23,6 +24,8 @@ from homeassistant.loader import bind_hass
|
|||
from homeassistant.util.percentage import (
|
||||
ordered_list_item_to_percentage,
|
||||
percentage_to_ordered_list_item,
|
||||
percentage_to_ranged_value,
|
||||
ranged_value_to_percentage,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -39,6 +42,8 @@ SUPPORT_DIRECTION = 4
|
|||
SUPPORT_PRESET_MODE = 8
|
||||
|
||||
SERVICE_SET_SPEED = "set_speed"
|
||||
SERVICE_INCREASE_SPEED = "increase_speed"
|
||||
SERVICE_DECREASE_SPEED = "decrease_speed"
|
||||
SERVICE_OSCILLATE = "oscillate"
|
||||
SERVICE_SET_DIRECTION = "set_direction"
|
||||
SERVICE_SET_PERCENTAGE = "set_percentage"
|
||||
|
@ -54,6 +59,7 @@ DIRECTION_REVERSE = "reverse"
|
|||
|
||||
ATTR_SPEED = "speed"
|
||||
ATTR_PERCENTAGE = "percentage"
|
||||
ATTR_PERCENTAGE_STEP = "percentage_step"
|
||||
ATTR_SPEED_LIST = "speed_list"
|
||||
ATTR_OSCILLATING = "oscillating"
|
||||
ATTR_DIRECTION = "direction"
|
||||
|
@ -142,6 +148,26 @@ async def async_setup(hass, config: dict):
|
|||
"async_set_speed_deprecated",
|
||||
[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(
|
||||
SERVICE_OSCILLATE,
|
||||
{vol.Required(ATTR_OSCILLATING): cv.boolean},
|
||||
|
@ -246,6 +272,33 @@ class FanEntity(ToggleEntity):
|
|||
else:
|
||||
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
|
||||
def set_preset_mode(self, preset_mode: str) -> None:
|
||||
"""Set new preset mode."""
|
||||
|
@ -408,6 +461,19 @@ class FanEntity(ToggleEntity):
|
|||
return self.speed_to_percentage(self.speed)
|
||||
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
|
||||
def speed_list(self) -> list:
|
||||
"""Get the list of available speeds."""
|
||||
|
@ -531,6 +597,7 @@ class FanEntity(ToggleEntity):
|
|||
if supported_features & SUPPORT_SET_SPEED:
|
||||
data[ATTR_SPEED] = self.speed
|
||||
data[ATTR_PERCENTAGE] = self.percentage
|
||||
data[ATTR_PERCENTAGE_STEP] = self.percentage_step
|
||||
|
||||
if (
|
||||
supported_features & SUPPORT_PRESET_MODE
|
||||
|
|
|
@ -100,3 +100,41 @@ set_direction:
|
|||
options:
|
||||
- "forward"
|
||||
- "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
|
||||
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.config_entries import ConfigEntry
|
||||
from homeassistant.helpers.typing import HomeAssistantType
|
||||
from homeassistant.util.percentage import (
|
||||
int_states_in_range,
|
||||
percentage_to_ranged_value,
|
||||
ranged_value_to_percentage,
|
||||
)
|
||||
|
@ -48,6 +49,13 @@ class ISYFanEntity(ISYNodeEntity, FanEntity):
|
|||
return None
|
||||
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
|
||||
def is_on(self) -> bool:
|
||||
"""Get if the fan is on."""
|
||||
|
@ -95,6 +103,13 @@ class ISYFanProgramEntity(ISYProgramEntity, FanEntity):
|
|||
return None
|
||||
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
|
||||
def is_on(self) -> bool:
|
||||
"""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.util.percentage import (
|
||||
int_states_in_range,
|
||||
percentage_to_ranged_value,
|
||||
ranged_value_to_percentage,
|
||||
)
|
||||
|
@ -68,6 +69,13 @@ class KNXFan(KnxEntity, FanEntity):
|
|||
)
|
||||
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(
|
||||
self,
|
||||
speed: Optional[str] = None,
|
||||
|
|
|
@ -48,6 +48,11 @@ class LutronCasetaFan(LutronCasetaDevice, FanEntity):
|
|||
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
|
||||
def supported_features(self) -> int:
|
||||
"""Flag supported features. Speed Only."""
|
||||
|
|
|
@ -9,6 +9,7 @@ from homeassistant.components.fan import (
|
|||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.util.percentage import (
|
||||
int_states_in_range,
|
||||
percentage_to_ranged_value,
|
||||
ranged_value_to_percentage,
|
||||
)
|
||||
|
@ -72,6 +73,11 @@ class ZwaveFan(ZWaveDeviceEntity, FanEntity):
|
|||
"""
|
||||
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
|
||||
def supported_features(self):
|
||||
"""Flag supported features."""
|
||||
|
|
|
@ -6,6 +6,7 @@ from pysmartthings import Capability
|
|||
|
||||
from homeassistant.components.fan import SUPPORT_SET_SPEED, FanEntity
|
||||
from homeassistant.util.percentage import (
|
||||
int_states_in_range,
|
||||
percentage_to_ranged_value,
|
||||
ranged_value_to_percentage,
|
||||
)
|
||||
|
@ -79,6 +80,11 @@ class SmartThingsFan(SmartThingsEntity, FanEntity):
|
|||
"""Return the current speed percentage."""
|
||||
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
|
||||
def supported_features(self) -> int:
|
||||
"""Flag supported features."""
|
||||
|
|
|
@ -46,6 +46,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||
|
||||
CONF_FANS = "fans"
|
||||
CONF_SPEED_LIST = "speeds"
|
||||
CONF_SPEED_COUNT = "speed_count"
|
||||
CONF_PRESET_MODES = "preset_modes"
|
||||
CONF_SPEED_TEMPLATE = "speed_template"
|
||||
CONF_PERCENTAGE_TEMPLATE = "percentage_template"
|
||||
|
@ -86,6 +87,7 @@ FAN_SCHEMA = vol.All(
|
|||
vol.Optional(CONF_SET_PRESET_MODE_ACTION): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(CONF_SET_OSCILLATING_ACTION): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(CONF_SET_DIRECTION_ACTION): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(CONF_SPEED_COUNT): vol.Coerce(int),
|
||||
vol.Optional(
|
||||
CONF_SPEED_LIST,
|
||||
default=[SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH],
|
||||
|
@ -126,6 +128,7 @@ async def _async_create_entities(hass, config):
|
|||
set_direction_action = device_config.get(CONF_SET_DIRECTION_ACTION)
|
||||
|
||||
speed_list = device_config[CONF_SPEED_LIST]
|
||||
speed_count = device_config.get(CONF_SPEED_COUNT)
|
||||
preset_modes = device_config.get(CONF_PRESET_MODES)
|
||||
unique_id = device_config.get(CONF_UNIQUE_ID)
|
||||
|
||||
|
@ -148,6 +151,7 @@ async def _async_create_entities(hass, config):
|
|||
set_preset_mode_action,
|
||||
set_oscillating_action,
|
||||
set_direction_action,
|
||||
speed_count,
|
||||
speed_list,
|
||||
preset_modes,
|
||||
unique_id,
|
||||
|
@ -185,6 +189,7 @@ class TemplateFan(TemplateEntity, FanEntity):
|
|||
set_preset_mode_action,
|
||||
set_oscillating_action,
|
||||
set_direction_action,
|
||||
speed_count,
|
||||
speed_list,
|
||||
preset_modes,
|
||||
unique_id,
|
||||
|
@ -260,6 +265,9 @@ class TemplateFan(TemplateEntity, FanEntity):
|
|||
|
||||
self._unique_id = unique_id
|
||||
|
||||
# Number of valid speeds
|
||||
self._speed_count = speed_count
|
||||
|
||||
# List of valid speeds
|
||||
self._speed_list = speed_list
|
||||
|
||||
|
@ -281,6 +289,11 @@ class TemplateFan(TemplateEntity, FanEntity):
|
|||
"""Flag 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
|
||||
def speed_list(self) -> list:
|
||||
"""Get the list of available speeds."""
|
||||
|
|
|
@ -102,6 +102,13 @@ class TuyaFanDevice(TuyaDevice, FanEntity):
|
|||
"""Oscillate the fan."""
|
||||
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
|
||||
def oscillating(self):
|
||||
"""Return current oscillating status."""
|
||||
|
|
|
@ -6,6 +6,7 @@ from homeassistant.components.fan import SUPPORT_SET_SPEED, FanEntity
|
|||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.util.percentage import (
|
||||
int_states_in_range,
|
||||
percentage_to_ranged_value,
|
||||
ranged_value_to_percentage,
|
||||
)
|
||||
|
@ -77,6 +78,11 @@ class VeSyncFanHA(VeSyncDevice, FanEntity):
|
|||
return ranged_value_to_percentage(SPEED_RANGE, current_level)
|
||||
return None
|
||||
|
||||
@property
|
||||
def speed_count(self) -> int:
|
||||
"""Return the number of speeds the fan supports."""
|
||||
return int_states_in_range(SPEED_RANGE)
|
||||
|
||||
@property
|
||||
def preset_modes(self):
|
||||
"""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.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.util.percentage import (
|
||||
int_states_in_range,
|
||||
percentage_to_ranged_value,
|
||||
ranged_value_to_percentage,
|
||||
)
|
||||
|
@ -130,6 +131,11 @@ class WemoHumidifier(WemoSubscriptionEntity, FanEntity):
|
|||
"""Return the current speed percentage."""
|
||||
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
|
||||
def supported_features(self) -> int:
|
||||
"""Flag supported features."""
|
||||
|
|
|
@ -87,6 +87,11 @@ class WiLightFan(WiLightDevice, FanEntity):
|
|||
return None
|
||||
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
|
||||
def current_direction(self) -> str:
|
||||
"""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.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.util.percentage import (
|
||||
int_states_in_range,
|
||||
percentage_to_ranged_value,
|
||||
ranged_value_to_percentage,
|
||||
)
|
||||
|
@ -68,6 +69,11 @@ class ZwaveFan(ZWaveDeviceEntity, FanEntity):
|
|||
"""Return the current speed percentage."""
|
||||
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
|
||||
def supported_features(self):
|
||||
"""Flag supported features."""
|
||||
|
|
|
@ -13,6 +13,7 @@ from homeassistant.config_entries import ConfigEntry
|
|||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.util.percentage import (
|
||||
int_states_in_range,
|
||||
percentage_to_ranged_value,
|
||||
ranged_value_to_percentage,
|
||||
)
|
||||
|
@ -96,6 +97,11 @@ class ZwaveFan(ZWaveBaseEntity, FanEntity):
|
|||
return None
|
||||
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
|
||||
def supported_features(self) -> int:
|
||||
"""Flag supported features."""
|
||||
|
|
|
@ -67,7 +67,7 @@ def ranged_value_to_percentage(
|
|||
(1,255), 127: 50
|
||||
(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(
|
||||
|
@ -84,4 +84,14 @@ def percentage_to_ranged_value(
|
|||
(1,255), 50: 127.5
|
||||
(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 + [
|
||||
"fan.percentage_limited_fan",
|
||||
]
|
||||
PERCENTAGE_MODEL_FANS = ["fan.percentage_full_fan", "fan.percentage_limited_fan"]
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
|
@ -397,6 +398,128 @@ async def test_set_percentage(hass, fan_entity_id):
|
|||
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)
|
||||
async def test_oscillate(hass, fan_entity_id):
|
||||
"""Test oscillating the fan."""
|
||||
|
|
|
@ -7,9 +7,12 @@ from homeassistant.components.fan import (
|
|||
ATTR_DIRECTION,
|
||||
ATTR_OSCILLATING,
|
||||
ATTR_PERCENTAGE,
|
||||
ATTR_PERCENTAGE_STEP,
|
||||
ATTR_PRESET_MODE,
|
||||
ATTR_SPEED,
|
||||
DOMAIN,
|
||||
SERVICE_DECREASE_SPEED,
|
||||
SERVICE_INCREASE_SPEED,
|
||||
SERVICE_OSCILLATE,
|
||||
SERVICE_SET_DIRECTION,
|
||||
SERVICE_SET_PERCENTAGE,
|
||||
|
@ -106,6 +109,38 @@ async def async_set_percentage(
|
|||
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(
|
||||
hass, entity_id=ENTITY_MATCH_ALL, direction: str = None
|
||||
) -> None:
|
||||
|
|
|
@ -19,6 +19,8 @@ def test_fanentity():
|
|||
assert len(fan.speed_list) == 0
|
||||
assert len(fan.preset_modes) == 0
|
||||
assert fan.supported_features == 0
|
||||
assert fan.percentage_step == 1
|
||||
assert fan.speed_count == 100
|
||||
assert fan.capability_attributes == {}
|
||||
# Test set_speed not required
|
||||
with pytest.raises(NotImplementedError):
|
||||
|
@ -43,6 +45,8 @@ async def test_async_fanentity(hass):
|
|||
assert len(fan.speed_list) == 0
|
||||
assert len(fan.preset_modes) == 0
|
||||
assert fan.supported_features == 0
|
||||
assert fan.percentage_step == 1
|
||||
assert fan.speed_count == 100
|
||||
assert fan.capability_attributes == {}
|
||||
# Test set_speed not required
|
||||
with pytest.raises(NotImplementedError):
|
||||
|
@ -57,3 +61,7 @@ async def test_async_fanentity(hass):
|
|||
await fan.async_turn_on()
|
||||
with pytest.raises(NotImplementedError):
|
||||
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') }}",
|
||||
"oscillating_template": "{{ states('input_select.osc') }}",
|
||||
"direction_template": "{{ states('input_select.direction') }}",
|
||||
"speed_count": "3",
|
||||
"set_percentage": {
|
||||
"service": "script.fans_set_speed",
|
||||
"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)
|
||||
|
||||
|
||||
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):
|
||||
"""Test set invalid speed when fan is in initial state."""
|
||||
await _register_components(hass)
|
||||
|
|
Loading…
Reference in New Issue