Add TURN_OFF/TURN_ON feature flags for fan (#121447)

This commit is contained in:
G Johansson 2024-07-19 11:35:24 +02:00 committed by GitHub
parent 172778053c
commit ca4c617d4b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
58 changed files with 858 additions and 132 deletions

View file

@ -43,7 +43,10 @@ class BAFFan(BAFEntity, FanEntity):
FanEntityFeature.SET_SPEED FanEntityFeature.SET_SPEED
| FanEntityFeature.DIRECTION | FanEntityFeature.DIRECTION
| FanEntityFeature.PRESET_MODE | FanEntityFeature.PRESET_MODE
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
) )
_enable_turn_on_off_backwards_compatibility = False
_attr_preset_modes = [PRESET_MODE_AUTO] _attr_preset_modes = [PRESET_MODE_AUTO]
_attr_speed_count = SPEED_COUNT _attr_speed_count = SPEED_COUNT
_attr_name = None _attr_name = None

View file

@ -32,7 +32,12 @@ async def async_setup_entry(
class BalboaPumpFanEntity(BalboaEntity, FanEntity): class BalboaPumpFanEntity(BalboaEntity, FanEntity):
"""Representation of a Balboa Spa pump fan entity.""" """Representation of a Balboa Spa pump fan entity."""
_attr_supported_features = FanEntityFeature.SET_SPEED _attr_supported_features = (
FanEntityFeature.SET_SPEED
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
)
_enable_turn_on_off_backwards_compatibility = False
_attr_translation_key = "pump" _attr_translation_key = "pump"
def __init__(self, control: SpaControl) -> None: def __init__(self, control: SpaControl) -> None:

View file

@ -69,7 +69,7 @@ class BondFan(BondEntity, FanEntity):
super().__init__(data, device) super().__init__(data, device)
if self._device.has_action(Action.BREEZE_ON): if self._device.has_action(Action.BREEZE_ON):
self._attr_preset_modes = [PRESET_MODE_BREEZE] self._attr_preset_modes = [PRESET_MODE_BREEZE]
features = FanEntityFeature(0) features = FanEntityFeature.TURN_OFF | FanEntityFeature.TURN_ON
if self._device.supports_speed(): if self._device.supports_speed():
features |= FanEntityFeature.SET_SPEED features |= FanEntityFeature.SET_SPEED
if self._device.supports_direction(): if self._device.supports_direction():

View file

@ -62,7 +62,13 @@ class ComfoConnectFan(FanEntity):
_attr_icon = "mdi:air-conditioner" _attr_icon = "mdi:air-conditioner"
_attr_should_poll = False _attr_should_poll = False
_attr_supported_features = FanEntityFeature.SET_SPEED | FanEntityFeature.PRESET_MODE _attr_supported_features = (
FanEntityFeature.SET_SPEED
| FanEntityFeature.PRESET_MODE
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
)
_enable_turn_on_off_backwards_compatibility = False
_attr_preset_modes = PRESET_MODES _attr_preset_modes = PRESET_MODES
current_speed: float | None = None current_speed: float | None = None

View file

@ -56,7 +56,12 @@ class DeconzFan(DeconzDevice[Light], FanEntity):
TYPE = DOMAIN TYPE = DOMAIN
_default_on_speed = LightFanSpeed.PERCENT_50 _default_on_speed = LightFanSpeed.PERCENT_50
_attr_supported_features = FanEntityFeature.SET_SPEED _attr_supported_features = (
FanEntityFeature.SET_SPEED
| FanEntityFeature.TURN_ON
| FanEntityFeature.TURN_OFF
)
_enable_turn_on_off_backwards_compatibility = False
def __init__(self, device: Light, hub: DeconzHub) -> None: def __init__(self, device: Light, hub: DeconzHub) -> None:
"""Set up fan.""" """Set up fan."""

View file

@ -15,9 +15,15 @@ PRESET_MODE_SLEEP = "sleep"
PRESET_MODE_ON = "on" PRESET_MODE_ON = "on"
FULL_SUPPORT = ( FULL_SUPPORT = (
FanEntityFeature.SET_SPEED | FanEntityFeature.OSCILLATE | FanEntityFeature.DIRECTION FanEntityFeature.SET_SPEED
| FanEntityFeature.OSCILLATE
| FanEntityFeature.DIRECTION
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
)
LIMITED_SUPPORT = (
FanEntityFeature.SET_SPEED | FanEntityFeature.TURN_OFF | FanEntityFeature.TURN_ON
) )
LIMITED_SUPPORT = FanEntityFeature.SET_SPEED
async def async_setup_entry( async def async_setup_entry(
@ -75,7 +81,9 @@ async def async_setup_entry(
hass, hass,
"fan5", "fan5",
"Preset Only Limited Fan", "Preset Only Limited Fan",
FanEntityFeature.PRESET_MODE, FanEntityFeature.PRESET_MODE
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON,
[ [
PRESET_MODE_AUTO, PRESET_MODE_AUTO,
PRESET_MODE_SMART, PRESET_MODE_SMART,
@ -92,6 +100,7 @@ class BaseDemoFan(FanEntity):
_attr_should_poll = False _attr_should_poll = False
_attr_translation_key = "demo" _attr_translation_key = "demo"
_enable_turn_on_off_backwards_compatibility = False
def __init__( def __init__(
self, self,

View file

@ -45,6 +45,7 @@ class EsphomeFan(EsphomeEntity[FanInfo, FanState], FanEntity):
"""A fan implementation for ESPHome.""" """A fan implementation for ESPHome."""
_supports_speed_levels: bool = True _supports_speed_levels: bool = True
_enable_turn_on_off_backwards_compatibility = False
async def async_set_percentage(self, percentage: int) -> None: async def async_set_percentage(self, percentage: int) -> None:
"""Set the speed percentage of the fan.""" """Set the speed percentage of the fan."""
@ -148,7 +149,7 @@ class EsphomeFan(EsphomeEntity[FanInfo, FanState], FanEntity):
api_version = self._api_version api_version = self._api_version
supports_speed_levels = api_version.major == 1 and api_version.minor > 3 supports_speed_levels = api_version.major == 1 and api_version.minor > 3
self._supports_speed_levels = supports_speed_levels self._supports_speed_levels = supports_speed_levels
flags = FanEntityFeature(0) flags = FanEntityFeature.TURN_OFF | FanEntityFeature.TURN_ON
if static_info.supports_oscillation: if static_info.supports_oscillation:
flags |= FanEntityFeature.OSCILLATE flags |= FanEntityFeature.OSCILLATE
if static_info.supports_speed: if static_info.supports_speed:

View file

@ -2,6 +2,7 @@
from __future__ import annotations from __future__ import annotations
import asyncio
from datetime import timedelta from datetime import timedelta
from enum import IntFlag from enum import IntFlag
import functools as ft import functools as ft
@ -30,6 +31,7 @@ from homeassistant.helpers.deprecation import (
) )
from homeassistant.helpers.entity import ToggleEntity, ToggleEntityDescription from homeassistant.helpers.entity import ToggleEntity, ToggleEntityDescription
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity_platform import EntityPlatform
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
from homeassistant.loader import bind_hass from homeassistant.loader import bind_hass
from homeassistant.util.percentage import ( from homeassistant.util.percentage import (
@ -53,6 +55,8 @@ class FanEntityFeature(IntFlag):
OSCILLATE = 2 OSCILLATE = 2
DIRECTION = 4 DIRECTION = 4
PRESET_MODE = 8 PRESET_MODE = 8
TURN_OFF = 16
TURN_ON = 32
# These SUPPORT_* constants are deprecated as of Home Assistant 2022.5. # These SUPPORT_* constants are deprecated as of Home Assistant 2022.5.
@ -132,9 +136,17 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
vol.Optional(ATTR_PRESET_MODE): cv.string, vol.Optional(ATTR_PRESET_MODE): cv.string,
}, },
"async_handle_turn_on_service", "async_handle_turn_on_service",
[FanEntityFeature.TURN_ON],
)
component.async_register_entity_service(
SERVICE_TURN_OFF, {}, "async_turn_off", [FanEntityFeature.TURN_OFF]
)
component.async_register_entity_service(
SERVICE_TOGGLE,
{},
"async_toggle",
[FanEntityFeature.TURN_OFF, FanEntityFeature.TURN_ON],
) )
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( component.async_register_entity_service(
SERVICE_INCREASE_SPEED, SERVICE_INCREASE_SPEED,
{ {
@ -228,6 +240,99 @@ class FanEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
_attr_speed_count: int _attr_speed_count: int
_attr_supported_features: FanEntityFeature = FanEntityFeature(0) _attr_supported_features: FanEntityFeature = FanEntityFeature(0)
__mod_supported_features: FanEntityFeature = FanEntityFeature(0)
# Integrations should set `_enable_turn_on_off_backwards_compatibility` to False
# once migrated and set the feature flags TURN_ON/TURN_OFF as needed.
_enable_turn_on_off_backwards_compatibility: bool = True
def __getattribute__(self, __name: str) -> Any:
"""Get attribute.
Modify return of `supported_features` to
include `_mod_supported_features` if attribute is set.
"""
if __name != "supported_features":
return super().__getattribute__(__name)
# Convert the supported features to ClimateEntityFeature.
# Remove this compatibility shim in 2025.1 or later.
_supported_features: FanEntityFeature = super().__getattribute__(
"supported_features"
)
_mod_supported_features: FanEntityFeature = super().__getattribute__(
"_FanEntity__mod_supported_features"
)
if type(_supported_features) is int: # noqa: E721
_features = FanEntityFeature(_supported_features)
self._report_deprecated_supported_features_values(_features)
else:
_features = _supported_features
if not _mod_supported_features:
return _features
# Add automatically calculated FanEntityFeature.TURN_OFF/TURN_ON to
# supported features and return it
return _features | _mod_supported_features
@callback
def add_to_platform_start(
self,
hass: HomeAssistant,
platform: EntityPlatform,
parallel_updates: asyncio.Semaphore | None,
) -> None:
"""Start adding an entity to a platform."""
super().add_to_platform_start(hass, platform, parallel_updates)
def _report_turn_on_off(feature: str, method: str) -> None:
"""Log warning not implemented turn on/off feature."""
report_issue = self._suggest_report_issue()
message = (
"Entity %s (%s) does not set FanEntityFeature.%s"
" but implements the %s method. Please %s"
)
_LOGGER.warning(
message,
self.entity_id,
type(self),
feature,
method,
report_issue,
)
# Adds FanEntityFeature.TURN_OFF/TURN_ON depending on service calls implemented
# This should be removed in 2025.2.
if self._enable_turn_on_off_backwards_compatibility is False:
# Return if integration has migrated already
return
supported_features = self.supported_features
if supported_features & (FanEntityFeature.TURN_ON | FanEntityFeature.TURN_OFF):
# The entity supports both turn_on and turn_off, the backwards compatibility
# checks are not needed
return
if not supported_features & FanEntityFeature.TURN_OFF and (
type(self).async_turn_off is not ToggleEntity.async_turn_off
or type(self).turn_off is not ToggleEntity.turn_off
):
# turn_off implicitly supported by implementing turn_off method
_report_turn_on_off("TURN_OFF", "turn_off")
self.__mod_supported_features |= ( # pylint: disable=unused-private-member
FanEntityFeature.TURN_OFF
)
if not supported_features & FanEntityFeature.TURN_ON and (
type(self).async_turn_on is not FanEntity.async_turn_on
or type(self).turn_on is not FanEntity.turn_on
):
# turn_on implicitly supported by implementing turn_on method
_report_turn_on_off("TURN_ON", "turn_on")
self.__mod_supported_features |= ( # pylint: disable=unused-private-member
FanEntityFeature.TURN_ON
)
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."""
raise NotImplementedError raise NotImplementedError
@ -388,7 +493,7 @@ class FanEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
def capability_attributes(self) -> dict[str, list[str] | None]: def capability_attributes(self) -> dict[str, list[str] | None]:
"""Return capability attributes.""" """Return capability attributes."""
attrs = {} attrs = {}
supported_features = self.supported_features_compat supported_features = self.supported_features
if ( if (
FanEntityFeature.SET_SPEED in supported_features FanEntityFeature.SET_SPEED in supported_features
@ -403,7 +508,7 @@ class FanEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
def state_attributes(self) -> dict[str, float | str | None]: def state_attributes(self) -> dict[str, float | str | None]:
"""Return optional state attributes.""" """Return optional state attributes."""
data: dict[str, float | str | None] = {} data: dict[str, float | str | None] = {}
supported_features = self.supported_features_compat supported_features = self.supported_features
if FanEntityFeature.DIRECTION in supported_features: if FanEntityFeature.DIRECTION in supported_features:
data[ATTR_DIRECTION] = self.current_direction data[ATTR_DIRECTION] = self.current_direction
@ -427,19 +532,6 @@ class FanEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
"""Flag supported features.""" """Flag supported features."""
return self._attr_supported_features return self._attr_supported_features
@property
def supported_features_compat(self) -> FanEntityFeature:
"""Return the supported features as FanEntityFeature.
Remove this compatibility shim in 2025.1 or later.
"""
features = self.supported_features
if type(features) is int: # noqa: E721
new_features = FanEntityFeature(features)
self._report_deprecated_supported_features_values(new_features)
return new_features
return features
@cached_property @cached_property
def preset_mode(self) -> str | None: def preset_mode(self) -> str | None:
"""Return the current preset mode, e.g., auto, smart, interval, favorite. """Return the current preset mode, e.g., auto, smart, interval, favorite.

View file

@ -31,6 +31,8 @@ turn_on:
target: target:
entity: entity:
domain: fan domain: fan
supported_features:
- fan.FanEntityFeature.TURN_ON
fields: fields:
percentage: percentage:
filter: filter:
@ -53,6 +55,8 @@ turn_off:
target: target:
entity: entity:
domain: fan domain: fan
supported_features:
- fan.FanEntityFeature.TURN_OFF
oscillate: oscillate:
target: target:

View file

@ -65,7 +65,13 @@ async def async_setup_entry(
class Fan(CoordinatorEntity[FjaraskupanCoordinator], FanEntity): class Fan(CoordinatorEntity[FjaraskupanCoordinator], FanEntity):
"""Fan entity.""" """Fan entity."""
_attr_supported_features = FanEntityFeature.SET_SPEED | FanEntityFeature.PRESET_MODE _attr_supported_features = (
FanEntityFeature.SET_SPEED
| FanEntityFeature.PRESET_MODE
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
)
_enable_turn_on_off_backwards_compatibility = False
_attr_has_entity_name = True _attr_has_entity_name = True
_attr_name = None _attr_name = None

View file

@ -40,6 +40,7 @@ class FreedomproFan(CoordinatorEntity[FreedomproDataUpdateCoordinator], FanEntit
_attr_name = None _attr_name = None
_attr_is_on = False _attr_is_on = False
_attr_percentage = 0 _attr_percentage = 0
_enable_turn_on_off_backwards_compatibility = False
def __init__( def __init__(
self, self,
@ -62,8 +63,11 @@ class FreedomproFan(CoordinatorEntity[FreedomproDataUpdateCoordinator], FanEntit
model=device["type"], model=device["type"],
name=device["name"], name=device["name"],
) )
self._attr_supported_features = (
FanEntityFeature.TURN_OFF | FanEntityFeature.TURN_ON
)
if "rotationSpeed" in self._characteristics: if "rotationSpeed" in self._characteristics:
self._attr_supported_features = FanEntityFeature.SET_SPEED self._attr_supported_features |= FanEntityFeature.SET_SPEED
@property @property
def is_on(self) -> bool | None: def is_on(self) -> bool | None:

View file

@ -47,6 +47,8 @@ SUPPORTED_FLAGS = {
FanEntityFeature.SET_SPEED, FanEntityFeature.SET_SPEED,
FanEntityFeature.DIRECTION, FanEntityFeature.DIRECTION,
FanEntityFeature.OSCILLATE, FanEntityFeature.OSCILLATE,
FanEntityFeature.TURN_OFF,
FanEntityFeature.TURN_ON,
} }
DEFAULT_NAME = "Fan Group" DEFAULT_NAME = "Fan Group"
@ -107,6 +109,7 @@ class FanGroup(GroupEntity, FanEntity):
"""Representation of a FanGroup.""" """Representation of a FanGroup."""
_attr_available: bool = False _attr_available: bool = False
_enable_turn_on_off_backwards_compatibility = False
def __init__(self, unique_id: str | None, name: str, entities: list[str]) -> None: def __init__(self, unique_id: str | None, name: str, entities: list[str]) -> None:
"""Initialize a FanGroup entity.""" """Initialize a FanGroup entity."""
@ -200,11 +203,15 @@ class FanGroup(GroupEntity, FanEntity):
if percentage is not None: if percentage is not None:
await self.async_set_percentage(percentage) await self.async_set_percentage(percentage)
return return
await self._async_call_all_entities(SERVICE_TURN_ON) await self._async_call_supported_entities(
SERVICE_TURN_ON, FanEntityFeature.TURN_ON, {}
)
async def async_turn_off(self, **kwargs: Any) -> None: async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the fans off.""" """Turn the fans off."""
await self._async_call_all_entities(SERVICE_TURN_OFF) await self._async_call_supported_entities(
SERVICE_TURN_OFF, FanEntityFeature.TURN_OFF, {}
)
async def _async_call_supported_entities( async def _async_call_supported_entities(
self, service: str, support_flag: int, data: dict[str, Any] self, service: str, support_flag: int, data: dict[str, Any]

View file

@ -42,6 +42,7 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity):
# This must be set in subclasses to the name of a boolean characteristic # This must be set in subclasses to the name of a boolean characteristic
# that controls whether the fan is on or off. # that controls whether the fan is on or off.
on_characteristic: str on_characteristic: str
_enable_turn_on_off_backwards_compatibility = False
@callback @callback
def _async_reconfigure(self) -> None: def _async_reconfigure(self) -> None:
@ -113,7 +114,7 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity):
@cached_property @cached_property
def supported_features(self) -> FanEntityFeature: def supported_features(self) -> FanEntityFeature:
"""Flag supported features.""" """Flag supported features."""
features = FanEntityFeature(0) features = FanEntityFeature.TURN_OFF | FanEntityFeature.TURN_ON
if self.service.has(CharacteristicsTypes.ROTATION_DIRECTION): if self.service.has(CharacteristicsTypes.ROTATION_DIRECTION):
features |= FanEntityFeature.DIRECTION features |= FanEntityFeature.DIRECTION

View file

@ -50,8 +50,13 @@ async def async_setup_entry(
class InsteonFanEntity(InsteonEntity, FanEntity): class InsteonFanEntity(InsteonEntity, FanEntity):
"""An INSTEON fan entity.""" """An INSTEON fan entity."""
_attr_supported_features = FanEntityFeature.SET_SPEED _attr_supported_features = (
FanEntityFeature.SET_SPEED
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
)
_attr_speed_count = 3 _attr_speed_count = 3
_enable_turn_on_off_backwards_compatibility = False
@property @property
def percentage(self) -> int | None: def percentage(self) -> int | None:

View file

@ -75,7 +75,12 @@ class IntellifireFan(IntellifireEntity, FanEntity):
"""Fan entity for the fireplace.""" """Fan entity for the fireplace."""
entity_description: IntellifireFanEntityDescription entity_description: IntellifireFanEntityDescription
_attr_supported_features = FanEntityFeature.SET_SPEED _attr_supported_features = (
FanEntityFeature.SET_SPEED
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
)
_enable_turn_on_off_backwards_compatibility = False
@property @property
def is_on(self) -> bool: def is_on(self) -> bool:

View file

@ -48,7 +48,12 @@ async def async_setup_entry(
class ISYFanEntity(ISYNodeEntity, FanEntity): class ISYFanEntity(ISYNodeEntity, FanEntity):
"""Representation of an ISY fan device.""" """Representation of an ISY fan device."""
_attr_supported_features = FanEntityFeature.SET_SPEED _attr_supported_features = (
FanEntityFeature.SET_SPEED
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
)
_enable_turn_on_off_backwards_compatibility = False
@property @property
def percentage(self) -> int | None: def percentage(self) -> int | None:

View file

@ -43,6 +43,7 @@ class KNXFan(KnxEntity, FanEntity):
"""Representation of a KNX fan.""" """Representation of a KNX fan."""
_device: XknxFan _device: XknxFan
_enable_turn_on_off_backwards_compatibility = False
def __init__(self, xknx: XKNX, config: ConfigType) -> None: def __init__(self, xknx: XKNX, config: ConfigType) -> None:
"""Initialize of KNX fan.""" """Initialize of KNX fan."""
@ -79,7 +80,11 @@ class KNXFan(KnxEntity, FanEntity):
@property @property
def supported_features(self) -> FanEntityFeature: def supported_features(self) -> FanEntityFeature:
"""Flag supported features.""" """Flag supported features."""
flags = FanEntityFeature.SET_SPEED flags = (
FanEntityFeature.SET_SPEED
| FanEntityFeature.TURN_ON
| FanEntityFeature.TURN_OFF
)
if self._device.supports_oscillation: if self._device.supports_oscillation:
flags |= FanEntityFeature.OSCILLATE flags |= FanEntityFeature.OSCILLATE

View file

@ -44,9 +44,14 @@ class LutronFan(LutronDevice, FanEntity):
_attr_name = None _attr_name = None
_attr_should_poll = False _attr_should_poll = False
_attr_speed_count = 3 _attr_speed_count = 3
_attr_supported_features = FanEntityFeature.SET_SPEED _attr_supported_features = (
FanEntityFeature.SET_SPEED
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
)
_lutron_device: Output _lutron_device: Output
_prev_percentage: int | None = None _prev_percentage: int | None = None
_enable_turn_on_off_backwards_compatibility = False
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."""

View file

@ -40,8 +40,13 @@ async def async_setup_entry(
class LutronCasetaFan(LutronCasetaDeviceUpdatableEntity, FanEntity): class LutronCasetaFan(LutronCasetaDeviceUpdatableEntity, FanEntity):
"""Representation of a Lutron Caseta fan. Including Fan Speed.""" """Representation of a Lutron Caseta fan. Including Fan Speed."""
_attr_supported_features = FanEntityFeature.SET_SPEED _attr_supported_features = (
FanEntityFeature.SET_SPEED
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
)
_attr_speed_count = len(ORDERED_NAMED_FAN_SPEEDS) _attr_speed_count = len(ORDERED_NAMED_FAN_SPEEDS)
_enable_turn_on_off_backwards_compatibility = False
@property @property
def percentage(self) -> int | None: def percentage(self) -> int | None:

View file

@ -57,6 +57,7 @@ class MatterFan(MatterEntity, FanEntity):
"""Representation of a Matter fan.""" """Representation of a Matter fan."""
_last_known_preset_mode: str | None = None _last_known_preset_mode: str | None = None
_enable_turn_on_off_backwards_compatibility = False
async def async_turn_on( async def async_turn_on(
self, self,
@ -294,6 +295,10 @@ class MatterFan(MatterEntity, FanEntity):
if feature_map & FanControlFeature.kAirflowDirection: if feature_map & FanControlFeature.kAirflowDirection:
self._attr_supported_features |= FanEntityFeature.DIRECTION self._attr_supported_features |= FanEntityFeature.DIRECTION
self._attr_supported_features |= (
FanEntityFeature.TURN_OFF | FanEntityFeature.TURN_ON
)
# Discovery schema(s) to map Matter Attributes to HA entities # Discovery schema(s) to map Matter Attributes to HA entities
DISCOVERY_SCHEMAS = [ DISCOVERY_SCHEMAS = [

View file

@ -4,7 +4,7 @@ from __future__ import annotations
from typing import Any from typing import Any
from homeassistant.components.fan import FanEntity from homeassistant.components.fan import FanEntity, FanEntityFeature
from homeassistant.const import CONF_NAME from homeassistant.const import CONF_NAME
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
@ -38,6 +38,18 @@ async def async_setup_platform(
class ModbusFan(BaseSwitch, FanEntity): class ModbusFan(BaseSwitch, FanEntity):
"""Class representing a Modbus fan.""" """Class representing a Modbus fan."""
_enable_turn_on_off_backwards_compatibility = False
def __init__(
self, hass: HomeAssistant, hub: ModbusHub, config: dict[str, Any]
) -> None:
"""Initialize the fan."""
super().__init__(hass, hub, config)
if self.command_on is not None and self._command_off is not None:
self._attr_supported_features |= (
FanEntityFeature.TURN_OFF | FanEntityFeature.TURN_ON
)
async def async_turn_on( async def async_turn_on(
self, self,
percentage: int | None = None, percentage: int | None = None,

View file

@ -70,8 +70,14 @@ class ModernFormsFanEntity(FanEntity, ModernFormsDeviceEntity):
SPEED_RANGE = (1, 6) # off is not included SPEED_RANGE = (1, 6) # off is not included
_attr_supported_features = FanEntityFeature.DIRECTION | FanEntityFeature.SET_SPEED _attr_supported_features = (
FanEntityFeature.DIRECTION
| FanEntityFeature.SET_SPEED
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
)
_attr_translation_key = "fan" _attr_translation_key = "fan"
_enable_turn_on_off_backwards_compatibility = False
def __init__( def __init__(
self, entry_id: str, coordinator: ModernFormsDataUpdateCoordinator self, entry_id: str, coordinator: ModernFormsDataUpdateCoordinator

View file

@ -224,6 +224,7 @@ class MqttFan(MqttEntity, FanEntity):
_optimistic_preset_mode: bool _optimistic_preset_mode: bool
_payload: dict[str, Any] _payload: dict[str, Any]
_speed_range: tuple[int, int] _speed_range: tuple[int, int]
_enable_turn_on_off_backwards_compatibility = False
@staticmethod @staticmethod
def config_schema() -> VolSchemaType: def config_schema() -> VolSchemaType:
@ -289,7 +290,9 @@ class MqttFan(MqttEntity, FanEntity):
optimistic or self._topic[CONF_PRESET_MODE_STATE_TOPIC] is None optimistic or self._topic[CONF_PRESET_MODE_STATE_TOPIC] is None
) )
self._attr_supported_features = FanEntityFeature(0) self._attr_supported_features = (
FanEntityFeature.TURN_OFF | FanEntityFeature.TURN_ON
)
self._attr_supported_features |= ( self._attr_supported_features |= (
self._topic[CONF_OSCILLATION_COMMAND_TOPIC] is not None self._topic[CONF_OSCILLATION_COMMAND_TOPIC] is not None
and FanEntityFeature.OSCILLATE and FanEntityFeature.OSCILLATE

View file

@ -51,6 +51,7 @@ class NetatmoFan(NetatmoModuleEntity, FanEntity):
_attr_configuration_url = CONF_URL_CONTROL _attr_configuration_url = CONF_URL_CONTROL
_attr_name = None _attr_name = None
device: NaModules.Fan device: NaModules.Fan
_enable_turn_on_off_backwards_compatibility = False
def __init__(self, netatmo_device: NetatmoDevice) -> None: def __init__(self, netatmo_device: NetatmoDevice) -> None:
"""Initialize of Netatmo fan.""" """Initialize of Netatmo fan."""

View file

@ -49,7 +49,13 @@ async def async_setup_entry(
class RabbitAirFanEntity(RabbitAirBaseEntity, FanEntity): class RabbitAirFanEntity(RabbitAirBaseEntity, FanEntity):
"""Fan control functions of the Rabbit Air air purifier.""" """Fan control functions of the Rabbit Air air purifier."""
_attr_supported_features = FanEntityFeature.PRESET_MODE | FanEntityFeature.SET_SPEED _attr_supported_features = (
FanEntityFeature.PRESET_MODE
| FanEntityFeature.SET_SPEED
| FanEntityFeature.TURN_ON
| FanEntityFeature.TURN_OFF
)
_enable_turn_on_off_backwards_compatibility = False
def __init__( def __init__(
self, self,

View file

@ -122,7 +122,12 @@ class RensonFan(RensonEntity, FanEntity):
_attr_has_entity_name = True _attr_has_entity_name = True
_attr_name = None _attr_name = None
_attr_translation_key = "fan" _attr_translation_key = "fan"
_attr_supported_features = FanEntityFeature.SET_SPEED _attr_supported_features = (
FanEntityFeature.SET_SPEED
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
)
_enable_turn_on_off_backwards_compatibility = False
def __init__(self, api: RensonVentilation, coordinator: RensonCoordinator) -> None: def __init__(self, api: RensonVentilation, coordinator: RensonCoordinator) -> None:
"""Initialize the Renson fan.""" """Initialize the Renson fan."""

View file

@ -70,6 +70,7 @@ class SmartThingsFan(SmartThingsEntity, FanEntity):
"""Define a SmartThings Fan.""" """Define a SmartThings Fan."""
_attr_speed_count = int_states_in_range(SPEED_RANGE) _attr_speed_count = int_states_in_range(SPEED_RANGE)
_enable_turn_on_off_backwards_compatibility = False
def __init__(self, device): def __init__(self, device):
"""Init the class.""" """Init the class."""
@ -77,7 +78,7 @@ class SmartThingsFan(SmartThingsEntity, FanEntity):
self._attr_supported_features = self._determine_features() self._attr_supported_features = self._determine_features()
def _determine_features(self): def _determine_features(self):
flags = FanEntityFeature(0) flags = FanEntityFeature.TURN_OFF | FanEntityFeature.TURN_ON
if self._device.get_capability(Capability.fan_speed): if self._device.get_capability(Capability.fan_speed):
flags |= FanEntityFeature.SET_SPEED flags |= FanEntityFeature.SET_SPEED

View file

@ -46,7 +46,12 @@ class SmartyFan(FanEntity):
_attr_icon = "mdi:air-conditioner" _attr_icon = "mdi:air-conditioner"
_attr_should_poll = False _attr_should_poll = False
_attr_supported_features = FanEntityFeature.SET_SPEED _attr_supported_features = (
FanEntityFeature.SET_SPEED
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
)
_enable_turn_on_off_backwards_compatibility = False
def __init__(self, name, smarty): def __init__(self, name, smarty):
"""Initialize the entity.""" """Initialize the entity."""

View file

@ -75,10 +75,15 @@ class SnoozFan(FanEntity, RestoreEntity):
_attr_has_entity_name = True _attr_has_entity_name = True
_attr_name = None _attr_name = None
_attr_supported_features = FanEntityFeature.SET_SPEED _attr_supported_features = (
FanEntityFeature.SET_SPEED
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
)
_attr_should_poll = False _attr_should_poll = False
_is_on: bool | None = None _is_on: bool | None = None
_percentage: int | None = None _percentage: int | None = None
_enable_turn_on_off_backwards_compatibility = False
def __init__(self, data: SnoozConfigurationData) -> None: def __init__(self, data: SnoozConfigurationData) -> None:
"""Initialize a Snooz fan entity.""" """Initialize a Snooz fan entity."""

View file

@ -4,7 +4,11 @@ from __future__ import annotations
from typing import Any from typing import Any
from homeassistant.components.fan import DOMAIN as FAN_DOMAIN, FanEntity from homeassistant.components.fan import (
DOMAIN as FAN_DOMAIN,
FanEntity,
FanEntityFeature,
)
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_ENTITY_ID from homeassistant.const import CONF_ENTITY_ID
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@ -41,6 +45,9 @@ async def async_setup_entry(
class FanSwitch(BaseToggleEntity, FanEntity): class FanSwitch(BaseToggleEntity, FanEntity):
"""Represents a Switch as a Fan.""" """Represents a Switch as a Fan."""
_attr_supported_features = FanEntityFeature.TURN_OFF | FanEntityFeature.TURN_ON
_enable_turn_on_off_backwards_compatibility = False
@property @property
def is_on(self) -> bool | None: def is_on(self) -> bool | None:
"""Return true if the entity is on. """Return true if the entity is on.

View file

@ -65,9 +65,14 @@ class TasmotaFan(
): ):
"""Representation of a Tasmota fan.""" """Representation of a Tasmota fan."""
_attr_supported_features = FanEntityFeature.SET_SPEED _attr_supported_features = (
FanEntityFeature.SET_SPEED
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
)
_fan_speed = tasmota_const.FAN_SPEED_MEDIUM _fan_speed = tasmota_const.FAN_SPEED_MEDIUM
_tasmota_entity: tasmota_fan.TasmotaFan _tasmota_entity: tasmota_fan.TasmotaFan
_enable_turn_on_off_backwards_compatibility = False
def __init__(self, **kwds: Any) -> None: def __init__(self, **kwds: Any) -> None:
"""Initialize the Tasmota fan.""" """Initialize the Tasmota fan."""

View file

@ -124,6 +124,7 @@ class TemplateFan(TemplateEntity, FanEntity):
"""A template fan component.""" """A template fan component."""
_attr_should_poll = False _attr_should_poll = False
_enable_turn_on_off_backwards_compatibility = False
def __init__( def __init__(
self, self,
@ -195,6 +196,9 @@ class TemplateFan(TemplateEntity, FanEntity):
self._attr_supported_features |= FanEntityFeature.OSCILLATE self._attr_supported_features |= FanEntityFeature.OSCILLATE
if self._direction_template: if self._direction_template:
self._attr_supported_features |= FanEntityFeature.DIRECTION self._attr_supported_features |= FanEntityFeature.DIRECTION
self._attr_supported_features |= (
FanEntityFeature.TURN_OFF | FanEntityFeature.TURN_ON
)
self._attr_assumed_state = self._template is None self._attr_assumed_state = self._template is None

View file

@ -4,7 +4,7 @@ from __future__ import annotations
from typing import Any from typing import Any
from homeassistant.components.fan import FanEntity from homeassistant.components.fan import FanEntity, FanEntityFeature
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
@ -27,6 +27,8 @@ class ToloFan(ToloSaunaCoordinatorEntity, FanEntity):
"""Sauna fan control.""" """Sauna fan control."""
_attr_translation_key = "fan" _attr_translation_key = "fan"
_attr_supported_features = FanEntityFeature.TURN_OFF | FanEntityFeature.TURN_ON
_enable_turn_on_off_backwards_compatibility = False
def __init__( def __init__(
self, coordinator: ToloSaunaUpdateCoordinator, entry: ConfigEntry self, coordinator: ToloSaunaUpdateCoordinator, entry: ConfigEntry

View file

@ -59,7 +59,12 @@ class TPLinkFanEntity(CoordinatedTPLinkEntity, FanEntity):
"""Representation of a fan for a TPLink Fan device.""" """Representation of a fan for a TPLink Fan device."""
_attr_speed_count = int_states_in_range(SPEED_RANGE) _attr_speed_count = int_states_in_range(SPEED_RANGE)
_attr_supported_features = FanEntityFeature.SET_SPEED _attr_supported_features = (
FanEntityFeature.SET_SPEED
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
)
_enable_turn_on_off_backwards_compatibility = False
def __init__( def __init__(
self, self,

View file

@ -55,7 +55,12 @@ class TradfriAirPurifierFan(TradfriBaseEntity, FanEntity):
"""The platform class required by Home Assistant.""" """The platform class required by Home Assistant."""
_attr_name = None _attr_name = None
_attr_supported_features = FanEntityFeature.PRESET_MODE | FanEntityFeature.SET_SPEED _attr_supported_features = (
FanEntityFeature.PRESET_MODE
| FanEntityFeature.SET_SPEED
| FanEntityFeature.TURN_ON
| FanEntityFeature.TURN_OFF
)
_attr_preset_modes = [ATTR_AUTO] _attr_preset_modes = [ATTR_AUTO]
# These are the steps: # These are the steps:
# 0 = Off # 0 = Off
@ -64,6 +69,7 @@ class TradfriAirPurifierFan(TradfriBaseEntity, FanEntity):
# ... with step size 1 # ... with step size 1
# 50 = Max # 50 = Max
_attr_speed_count = ATTR_MAX_FAN_STEPS _attr_speed_count = ATTR_MAX_FAN_STEPS
_enable_turn_on_off_backwards_compatibility = False
def __init__( def __init__(
self, self,

View file

@ -66,6 +66,7 @@ class TuyaFanEntity(TuyaEntity, FanEntity):
_speeds: EnumTypeData | None = None _speeds: EnumTypeData | None = None
_switch: DPCode | None = None _switch: DPCode | None = None
_attr_name = None _attr_name = None
_enable_turn_on_off_backwards_compatibility = False
def __init__( def __init__(
self, self,
@ -116,6 +117,10 @@ class TuyaFanEntity(TuyaEntity, FanEntity):
): ):
self._direction = enum_type self._direction = enum_type
self._attr_supported_features |= FanEntityFeature.DIRECTION self._attr_supported_features |= FanEntityFeature.DIRECTION
if self._switch is not None:
self._attr_supported_features |= (
FanEntityFeature.TURN_ON | FanEntityFeature.TURN_OFF
)
def set_preset_mode(self, preset_mode: str) -> None: def set_preset_mode(self, preset_mode: str) -> None:
"""Set the preset mode of the fan.""" """Set the preset mode of the fan."""

View file

@ -77,7 +77,13 @@ class ValloxFanEntity(ValloxEntity, FanEntity):
"""Representation of the fan.""" """Representation of the fan."""
_attr_name = None _attr_name = None
_attr_supported_features = FanEntityFeature.PRESET_MODE | FanEntityFeature.SET_SPEED _attr_supported_features = (
FanEntityFeature.PRESET_MODE
| FanEntityFeature.SET_SPEED
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
)
_enable_turn_on_off_backwards_compatibility = False
def __init__( def __init__(
self, self,

View file

@ -84,8 +84,14 @@ def _setup_entities(devices, async_add_entities):
class VeSyncFanHA(VeSyncDevice, FanEntity): class VeSyncFanHA(VeSyncDevice, FanEntity):
"""Representation of a VeSync fan.""" """Representation of a VeSync fan."""
_attr_supported_features = FanEntityFeature.SET_SPEED | FanEntityFeature.PRESET_MODE _attr_supported_features = (
FanEntityFeature.SET_SPEED
| FanEntityFeature.PRESET_MODE
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
)
_attr_name = None _attr_name = None
_enable_turn_on_off_backwards_compatibility = False
def __init__(self, fan) -> None: def __init__(self, fan) -> None:
"""Initialize the VeSync fan device.""" """Initialize the VeSync fan device."""

View file

@ -74,9 +74,14 @@ async def async_setup_entry(
class WemoHumidifier(WemoBinaryStateEntity, FanEntity): class WemoHumidifier(WemoBinaryStateEntity, FanEntity):
"""Representation of a WeMo humidifier.""" """Representation of a WeMo humidifier."""
_attr_supported_features = FanEntityFeature.SET_SPEED _attr_supported_features = (
FanEntityFeature.SET_SPEED
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
)
wemo: Humidifier wemo: Humidifier
_last_fan_on_mode: FanMode _last_fan_on_mode: FanMode
_enable_turn_on_off_backwards_compatibility = False
def __init__(self, coordinator: DeviceCoordinator) -> None: def __init__(self, coordinator: DeviceCoordinator) -> None:
"""Initialize the WeMo switch.""" """Initialize the WeMo switch."""

View file

@ -57,7 +57,13 @@ class WiLightFan(WiLightDevice, FanEntity):
_attr_name = None _attr_name = None
_attr_speed_count = len(ORDERED_NAMED_FAN_SPEEDS) _attr_speed_count = len(ORDERED_NAMED_FAN_SPEEDS)
_attr_supported_features = FanEntityFeature.SET_SPEED | FanEntityFeature.DIRECTION _attr_supported_features = (
FanEntityFeature.SET_SPEED
| FanEntityFeature.DIRECTION
| FanEntityFeature.TURN_ON
| FanEntityFeature.TURN_OFF
)
_enable_turn_on_off_backwards_compatibility = False
def __init__(self, api_device: PyWiLightDevice, index: str, item_name: str) -> None: def __init__(self, api_device: PyWiLightDevice, index: str, item_name: str) -> None:
"""Initialize the device.""" """Initialize the device."""

View file

@ -294,6 +294,7 @@ class XiaomiGenericDevice(XiaomiCoordinatedMiioEntity, FanEntity):
"""Representation of a generic Xiaomi device.""" """Representation of a generic Xiaomi device."""
_attr_name = None _attr_name = None
_enable_turn_on_off_backwards_compatibility = False
def __init__(self, device, entry, unique_id, coordinator): def __init__(self, device, entry, unique_id, coordinator):
"""Initialize the generic Xiaomi device.""" """Initialize the generic Xiaomi device."""
@ -479,6 +480,9 @@ class XiaomiAirPurifier(XiaomiGenericAirPurifier):
self._preset_modes = PRESET_MODES_AIRPURIFIER self._preset_modes = PRESET_MODES_AIRPURIFIER
self._attr_supported_features = FanEntityFeature.PRESET_MODE self._attr_supported_features = FanEntityFeature.PRESET_MODE
self._speed_count = 1 self._speed_count = 1
self._attr_supported_features |= (
FanEntityFeature.TURN_OFF | FanEntityFeature.TURN_ON
)
self._state = self.coordinator.data.is_on self._state = self.coordinator.data.is_on
self._state_attrs.update( self._state_attrs.update(
@ -609,7 +613,11 @@ class XiaomiAirPurifierMB4(XiaomiGenericAirPurifier):
self._device_features = FEATURE_FLAGS_AIRPURIFIER_3C self._device_features = FEATURE_FLAGS_AIRPURIFIER_3C
self._preset_modes = PRESET_MODES_AIRPURIFIER_3C self._preset_modes = PRESET_MODES_AIRPURIFIER_3C
self._attr_supported_features = FanEntityFeature.PRESET_MODE self._attr_supported_features = (
FanEntityFeature.PRESET_MODE
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
)
self._state = self.coordinator.data.is_on self._state = self.coordinator.data.is_on
self._mode = self.coordinator.data.mode.value self._mode = self.coordinator.data.mode.value
@ -663,7 +671,10 @@ class XiaomiAirFresh(XiaomiGenericAirPurifier):
self._speed_count = 4 self._speed_count = 4
self._preset_modes = PRESET_MODES_AIRFRESH self._preset_modes = PRESET_MODES_AIRFRESH
self._attr_supported_features = ( self._attr_supported_features = (
FanEntityFeature.SET_SPEED | FanEntityFeature.PRESET_MODE FanEntityFeature.SET_SPEED
| FanEntityFeature.PRESET_MODE
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
) )
self._state = self.coordinator.data.is_on self._state = self.coordinator.data.is_on
@ -756,7 +767,10 @@ class XiaomiAirFreshA1(XiaomiGenericAirPurifier):
self._device_features = FEATURE_FLAGS_AIRFRESH_A1 self._device_features = FEATURE_FLAGS_AIRFRESH_A1
self._preset_modes = PRESET_MODES_AIRFRESH_A1 self._preset_modes = PRESET_MODES_AIRFRESH_A1
self._attr_supported_features = ( self._attr_supported_features = (
FanEntityFeature.SET_SPEED | FanEntityFeature.PRESET_MODE FanEntityFeature.SET_SPEED
| FanEntityFeature.PRESET_MODE
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
) )
self._state = self.coordinator.data.is_on self._state = self.coordinator.data.is_on
@ -851,6 +865,8 @@ class XiaomiGenericFan(XiaomiGenericDevice):
FanEntityFeature.SET_SPEED FanEntityFeature.SET_SPEED
| FanEntityFeature.OSCILLATE | FanEntityFeature.OSCILLATE
| FanEntityFeature.PRESET_MODE | FanEntityFeature.PRESET_MODE
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
) )
if self._model != MODEL_FAN_1C: if self._model != MODEL_FAN_1C:
self._attr_supported_features |= FanEntityFeature.DIRECTION self._attr_supported_features |= FanEntityFeature.DIRECTION

View file

@ -43,8 +43,13 @@ async def async_setup_entry(
class ZhaFan(FanEntity, ZHAEntity): class ZhaFan(FanEntity, ZHAEntity):
"""Representation of a ZHA fan.""" """Representation of a ZHA fan."""
_attr_supported_features = FanEntityFeature.SET_SPEED _attr_supported_features = (
FanEntityFeature.SET_SPEED
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
)
_attr_translation_key: str = "fan" _attr_translation_key: str = "fan"
_enable_turn_on_off_backwards_compatibility = False
@property @property
def preset_mode(self) -> str | None: def preset_mode(self) -> str | None:

View file

@ -78,7 +78,12 @@ async def async_setup_entry(
class ZwaveFan(ZWaveBaseEntity, FanEntity): class ZwaveFan(ZWaveBaseEntity, FanEntity):
"""Representation of a Z-Wave fan.""" """Representation of a Z-Wave fan."""
_attr_supported_features = FanEntityFeature.SET_SPEED _attr_supported_features = (
FanEntityFeature.SET_SPEED
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
)
_enable_turn_on_off_backwards_compatibility = False
def __init__( def __init__(
self, config_entry: ConfigEntry, driver: Driver, info: ZwaveDiscoveryInfo self, config_entry: ConfigEntry, driver: Driver, info: ZwaveDiscoveryInfo
@ -249,7 +254,11 @@ class ValueMappingZwaveFan(ZwaveFan):
@property @property
def supported_features(self) -> FanEntityFeature: def supported_features(self) -> FanEntityFeature:
"""Flag supported features.""" """Flag supported features."""
flags = FanEntityFeature.SET_SPEED flags = (
FanEntityFeature.SET_SPEED
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
)
if self.has_fan_value_mapping and self.fan_value_mapping.presets: if self.has_fan_value_mapping and self.fan_value_mapping.presets:
flags |= FanEntityFeature.PRESET_MODE flags |= FanEntityFeature.PRESET_MODE
@ -382,7 +391,13 @@ class ZwaveThermostatFan(ZWaveBaseEntity, FanEntity):
@property @property
def supported_features(self) -> FanEntityFeature: def supported_features(self) -> FanEntityFeature:
"""Flag supported features.""" """Flag supported features."""
return FanEntityFeature.PRESET_MODE if not self._fan_off:
return FanEntityFeature.PRESET_MODE
return (
FanEntityFeature.PRESET_MODE
| FanEntityFeature.TURN_ON
| FanEntityFeature.TURN_OFF
)
@property @property
def fan_state(self) -> str | None: def fan_state(self) -> str | None:

View file

@ -44,7 +44,12 @@ async def async_setup_entry(
class ZWaveMeFan(ZWaveMeEntity, FanEntity): class ZWaveMeFan(ZWaveMeEntity, FanEntity):
"""Representation of a ZWaveMe Fan.""" """Representation of a ZWaveMe Fan."""
_attr_supported_features = FanEntityFeature.SET_SPEED _attr_supported_features = (
FanEntityFeature.SET_SPEED
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
)
_enable_turn_on_off_backwards_compatibility = False
@property @property
def percentage(self) -> int: def percentage(self) -> int:

View file

@ -28,7 +28,7 @@
'original_name': 'Pump 1', 'original_name': 'Pump 1',
'platform': 'balboa', 'platform': 'balboa',
'previous_unique_id': None, 'previous_unique_id': None,
'supported_features': <FanEntityFeature: 1>, 'supported_features': <FanEntityFeature: 49>,
'translation_key': 'pump', 'translation_key': 'pump',
'unique_id': 'FakeSpa-Pump 1-c0ffee', 'unique_id': 'FakeSpa-Pump 1-c0ffee',
'unit_of_measurement': None, 'unit_of_measurement': None,
@ -42,7 +42,7 @@
'percentage_step': 50.0, 'percentage_step': 50.0,
'preset_mode': None, 'preset_mode': None,
'preset_modes': None, 'preset_modes': None,
'supported_features': <FanEntityFeature: 1>, 'supported_features': <FanEntityFeature: 49>,
}), }),
'context': <ANY>, 'context': <ANY>,
'entity_id': 'fan.fakespa_pump_1', 'entity_id': 'fan.fakespa_pump_1',

View file

@ -0,0 +1,23 @@
"""Fixtures for Fan platform tests."""
from collections.abc import Generator
import pytest
from homeassistant.config_entries import ConfigFlow
from homeassistant.core import HomeAssistant
from tests.common import mock_config_flow, mock_platform
class MockFlow(ConfigFlow):
"""Test flow."""
@pytest.fixture
def config_flow_fixture(hass: HomeAssistant) -> Generator[None]:
"""Mock config flow."""
mock_platform(hass, "test.config_flow")
with mock_config_flow("test", MockFlow):
yield

View file

@ -1,5 +1,7 @@
"""Tests for fan platforms.""" """Tests for fan platforms."""
from unittest.mock import patch
import pytest import pytest
from homeassistant.components import fan from homeassistant.components import fan
@ -12,15 +14,23 @@ from homeassistant.components.fan import (
FanEntityFeature, FanEntityFeature,
NotValidPresetModeError, NotValidPresetModeError,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import SERVICE_TURN_OFF, SERVICE_TURN_ON
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
import homeassistant.helpers.entity_registry as er import homeassistant.helpers.entity_registry as er
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from .common import MockFan from .common import MockFan
from tests.common import ( from tests.common import (
MockConfigEntry,
MockModule,
MockPlatform,
help_test_all, help_test_all,
import_and_test_deprecated_constant_enum, import_and_test_deprecated_constant_enum,
mock_integration,
mock_platform,
setup_test_component_platform, setup_test_component_platform,
) )
@ -167,7 +177,10 @@ def test_deprecated_constants(
enum: fan.FanEntityFeature, enum: fan.FanEntityFeature,
) -> None: ) -> None:
"""Test deprecated constants.""" """Test deprecated constants."""
import_and_test_deprecated_constant_enum(caplog, fan, enum, "SUPPORT_", "2025.1") if not FanEntityFeature.TURN_OFF and not FanEntityFeature.TURN_ON:
import_and_test_deprecated_constant_enum(
caplog, fan, enum, "SUPPORT_", "2025.1"
)
def test_deprecated_supported_features_ints(caplog: pytest.LogCaptureFixture) -> None: def test_deprecated_supported_features_ints(caplog: pytest.LogCaptureFixture) -> None:
@ -180,11 +193,288 @@ def test_deprecated_supported_features_ints(caplog: pytest.LogCaptureFixture) ->
return 1 return 1
entity = MockFan() entity = MockFan()
assert entity.supported_features_compat is FanEntityFeature(1) assert entity.supported_features is FanEntityFeature(1)
assert "MockFan" in caplog.text assert "MockFan" in caplog.text
assert "is using deprecated supported features values" in caplog.text assert "is using deprecated supported features values" in caplog.text
assert "Instead it should use" in caplog.text assert "Instead it should use" in caplog.text
assert "FanEntityFeature.SET_SPEED" in caplog.text assert "FanEntityFeature.SET_SPEED" in caplog.text
caplog.clear() caplog.clear()
assert entity.supported_features_compat is FanEntityFeature(1) assert entity.supported_features is FanEntityFeature(1)
assert "is using deprecated supported features values" not in caplog.text assert "is using deprecated supported features values" not in caplog.text
async def test_warning_not_implemented_turn_on_off_feature(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, config_flow_fixture: None
) -> None:
"""Test adding feature flag and warn if missing when methods are set."""
called = []
class MockFanEntityTest(MockFan):
"""Mock Fan device."""
def turn_on(
self,
percentage: int | None = None,
preset_mode: str | None = None,
) -> None:
"""Turn on."""
called.append("turn_on")
def turn_off(self) -> None:
"""Turn off."""
called.append("turn_off")
async def async_setup_entry_init(
hass: HomeAssistant, config_entry: ConfigEntry
) -> bool:
"""Set up test config entry."""
await hass.config_entries.async_forward_entry_setups(config_entry, [DOMAIN])
return True
async def async_setup_entry_fan_platform(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up test fan platform via config entry."""
async_add_entities([MockFanEntityTest(name="test", entity_id="fan.test")])
mock_integration(
hass,
MockModule(
"test",
async_setup_entry=async_setup_entry_init,
),
built_in=False,
)
mock_platform(
hass,
"test.fan",
MockPlatform(async_setup_entry=async_setup_entry_fan_platform),
)
with patch.object(
MockFanEntityTest, "__module__", "tests.custom_components.fan.test_init"
):
config_entry = MockConfigEntry(domain="test")
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get("fan.test")
assert state is not None
assert (
"Entity fan.test (<class 'tests.custom_components.fan.test_init.test_warning_not_implemented_turn_on_off_feature.<locals>.MockFanEntityTest'>) "
"does not set FanEntityFeature.TURN_OFF but implements the turn_off method. Please report it to the author of the 'test' custom integration"
in caplog.text
)
assert (
"Entity fan.test (<class 'tests.custom_components.fan.test_init.test_warning_not_implemented_turn_on_off_feature.<locals>.MockFanEntityTest'>) "
"does not set FanEntityFeature.TURN_ON but implements the turn_on method. Please report it to the author of the 'test' custom integration"
in caplog.text
)
await hass.services.async_call(
DOMAIN,
SERVICE_TURN_ON,
{
"entity_id": "fan.test",
},
blocking=True,
)
await hass.services.async_call(
DOMAIN,
SERVICE_TURN_OFF,
{
"entity_id": "fan.test",
},
blocking=True,
)
assert len(called) == 2
assert "turn_on" in called
assert "turn_off" in called
async def test_no_warning_implemented_turn_on_off_feature(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, config_flow_fixture: None
) -> None:
"""Test no warning when feature flags are set."""
class MockFanEntityTest(MockFan):
"""Mock Fan device."""
_attr_supported_features = (
FanEntityFeature.DIRECTION
| FanEntityFeature.OSCILLATE
| FanEntityFeature.SET_SPEED
| FanEntityFeature.PRESET_MODE
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
)
async def async_setup_entry_init(
hass: HomeAssistant, config_entry: ConfigEntry
) -> bool:
"""Set up test config entry."""
await hass.config_entries.async_forward_entry_setups(config_entry, [DOMAIN])
return True
async def async_setup_entry_fan_platform(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up test fan platform via config entry."""
async_add_entities([MockFanEntityTest(name="test", entity_id="fan.test")])
mock_integration(
hass,
MockModule(
"test",
async_setup_entry=async_setup_entry_init,
),
built_in=False,
)
mock_platform(
hass,
"test.fan",
MockPlatform(async_setup_entry=async_setup_entry_fan_platform),
)
with patch.object(
MockFanEntityTest, "__module__", "tests.custom_components.fan.test_init"
):
config_entry = MockConfigEntry(domain="test")
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get("fan.test")
assert state is not None
assert "does not set FanEntityFeature.TURN_OFF" not in caplog.text
assert "does not set FanEntityFeature.TURN_ON" not in caplog.text
async def test_no_warning_integration_has_migrated(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, config_flow_fixture: None
) -> None:
"""Test no warning when integration migrated using `_enable_turn_on_off_backwards_compatibility`."""
class MockFanEntityTest(MockFan):
"""Mock Fan device."""
_enable_turn_on_off_backwards_compatibility = False
_attr_supported_features = (
FanEntityFeature.DIRECTION
| FanEntityFeature.OSCILLATE
| FanEntityFeature.SET_SPEED
| FanEntityFeature.PRESET_MODE
)
async def async_setup_entry_init(
hass: HomeAssistant, config_entry: ConfigEntry
) -> bool:
"""Set up test config entry."""
await hass.config_entries.async_forward_entry_setups(config_entry, [DOMAIN])
return True
async def async_setup_entry_fan_platform(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up test fan platform via config entry."""
async_add_entities([MockFanEntityTest(name="test", entity_id="fan.test")])
mock_integration(
hass,
MockModule(
"test",
async_setup_entry=async_setup_entry_init,
),
built_in=False,
)
mock_platform(
hass,
"test.fan",
MockPlatform(async_setup_entry=async_setup_entry_fan_platform),
)
with patch.object(
MockFanEntityTest, "__module__", "tests.custom_components.fan.test_init"
):
config_entry = MockConfigEntry(domain="test")
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get("fan.test")
assert state is not None
assert "does not set FanEntityFeature.TURN_OFF" not in caplog.text
assert "does not set FanEntityFeature.TURN_ON" not in caplog.text
async def test_no_warning_integration_implement_feature_flags(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, config_flow_fixture: None
) -> None:
"""Test no warning when integration uses the correct feature flags."""
class MockFanEntityTest(MockFan):
"""Mock Fan device."""
_attr_supported_features = (
FanEntityFeature.DIRECTION
| FanEntityFeature.OSCILLATE
| FanEntityFeature.SET_SPEED
| FanEntityFeature.PRESET_MODE
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
)
async def async_setup_entry_init(
hass: HomeAssistant, config_entry: ConfigEntry
) -> bool:
"""Set up test config entry."""
await hass.config_entries.async_forward_entry_setups(config_entry, [DOMAIN])
return True
async def async_setup_entry_fan_platform(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up test fan platform via config entry."""
async_add_entities([MockFanEntityTest(name="test", entity_id="fan.test")])
mock_integration(
hass,
MockModule(
"test",
async_setup_entry=async_setup_entry_init,
),
built_in=False,
)
mock_platform(
hass,
"test.fan",
MockPlatform(async_setup_entry=async_setup_entry_fan_platform),
)
with patch.object(
MockFanEntityTest, "__module__", "tests.custom_components.fan.test_init"
):
config_entry = MockConfigEntry(domain="test")
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get("fan.test")
assert state is not None
assert "does not set FanEntityFeature.TURN_OFF" not in caplog.text
assert "does not set FanEntityFeature.TURN_ON" not in caplog.text

View file

@ -103,7 +103,7 @@
'original_name': 'Airversa AP2 1808 AirPurifier', 'original_name': 'Airversa AP2 1808 AirPurifier',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
'supported_features': <FanEntityFeature: 1>, 'supported_features': <FanEntityFeature: 49>,
'translation_key': None, 'translation_key': None,
'unique_id': '00:00:00:00:00:00_1_32832', 'unique_id': '00:00:00:00:00:00_1_32832',
'unit_of_measurement': None, 'unit_of_measurement': None,
@ -115,7 +115,7 @@
'percentage_step': 20.0, 'percentage_step': 20.0,
'preset_mode': None, 'preset_mode': None,
'preset_modes': None, 'preset_modes': None,
'supported_features': <FanEntityFeature: 1>, 'supported_features': <FanEntityFeature: 49>,
}), }),
'entity_id': 'fan.airversa_ap2_1808_airpurifier', 'entity_id': 'fan.airversa_ap2_1808_airpurifier',
'state': 'off', 'state': 'off',
@ -6869,7 +6869,7 @@
'original_name': 'HAA-C718B3', 'original_name': 'HAA-C718B3',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
'supported_features': <FanEntityFeature: 1>, 'supported_features': <FanEntityFeature: 49>,
'translation_key': None, 'translation_key': None,
'unique_id': '00:00:00:00:00:00_1_8', 'unique_id': '00:00:00:00:00:00_1_8',
'unit_of_measurement': None, 'unit_of_measurement': None,
@ -6881,7 +6881,7 @@
'percentage_step': 33.333333333333336, 'percentage_step': 33.333333333333336,
'preset_mode': None, 'preset_mode': None,
'preset_modes': None, 'preset_modes': None,
'supported_features': <FanEntityFeature: 1>, 'supported_features': <FanEntityFeature: 49>,
}), }),
'entity_id': 'fan.haa_c718b3', 'entity_id': 'fan.haa_c718b3',
'state': 'on', 'state': 'on',
@ -7837,7 +7837,7 @@
'original_name': 'Ceiling Fan', 'original_name': 'Ceiling Fan',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
'supported_features': <FanEntityFeature: 1>, 'supported_features': <FanEntityFeature: 49>,
'translation_key': None, 'translation_key': None,
'unique_id': '00:00:00:00:00:00_766313939_8', 'unique_id': '00:00:00:00:00:00_766313939_8',
'unit_of_measurement': None, 'unit_of_measurement': None,
@ -7849,7 +7849,7 @@
'percentage_step': 1.0, 'percentage_step': 1.0,
'preset_mode': None, 'preset_mode': None,
'preset_modes': None, 'preset_modes': None,
'supported_features': <FanEntityFeature: 1>, 'supported_features': <FanEntityFeature: 49>,
}), }),
'entity_id': 'fan.ceiling_fan', 'entity_id': 'fan.ceiling_fan',
'state': 'off', 'state': 'off',
@ -8034,7 +8034,7 @@
'original_name': 'Living Room Fan', 'original_name': 'Living Room Fan',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
'supported_features': <FanEntityFeature: 5>, 'supported_features': <FanEntityFeature: 53>,
'translation_key': None, 'translation_key': None,
'unique_id': '00:00:00:00:00:00_1256851357_8', 'unique_id': '00:00:00:00:00:00_1256851357_8',
'unit_of_measurement': None, 'unit_of_measurement': None,
@ -8047,7 +8047,7 @@
'percentage_step': 1.0, 'percentage_step': 1.0,
'preset_mode': None, 'preset_mode': None,
'preset_modes': None, 'preset_modes': None,
'supported_features': <FanEntityFeature: 5>, 'supported_features': <FanEntityFeature: 53>,
}), }),
'entity_id': 'fan.living_room_fan', 'entity_id': 'fan.living_room_fan',
'state': 'off', 'state': 'off',
@ -8223,7 +8223,7 @@
'original_name': '89 Living Room', 'original_name': '89 Living Room',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
'supported_features': <FanEntityFeature: 3>, 'supported_features': <FanEntityFeature: 51>,
'translation_key': None, 'translation_key': None,
'unique_id': '00:00:00:00:00:00_1233851541_175', 'unique_id': '00:00:00:00:00:00_1233851541_175',
'unit_of_measurement': None, 'unit_of_measurement': None,
@ -8236,7 +8236,7 @@
'percentage_step': 33.333333333333336, 'percentage_step': 33.333333333333336,
'preset_mode': None, 'preset_mode': None,
'preset_modes': None, 'preset_modes': None,
'supported_features': <FanEntityFeature: 3>, 'supported_features': <FanEntityFeature: 51>,
}), }),
'entity_id': 'fan.89_living_room', 'entity_id': 'fan.89_living_room',
'state': 'on', 'state': 'on',
@ -9219,7 +9219,7 @@
'original_name': 'Ceiling Fan', 'original_name': 'Ceiling Fan',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
'supported_features': <FanEntityFeature: 1>, 'supported_features': <FanEntityFeature: 49>,
'translation_key': None, 'translation_key': None,
'unique_id': '00:00:00:00:00:00_766313939_8', 'unique_id': '00:00:00:00:00:00_766313939_8',
'unit_of_measurement': None, 'unit_of_measurement': None,
@ -9231,7 +9231,7 @@
'percentage_step': 1.0, 'percentage_step': 1.0,
'preset_mode': None, 'preset_mode': None,
'preset_modes': None, 'preset_modes': None,
'supported_features': <FanEntityFeature: 1>, 'supported_features': <FanEntityFeature: 49>,
}), }),
'entity_id': 'fan.ceiling_fan', 'entity_id': 'fan.ceiling_fan',
'state': 'off', 'state': 'off',
@ -9416,7 +9416,7 @@
'original_name': 'Living Room Fan', 'original_name': 'Living Room Fan',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
'supported_features': <FanEntityFeature: 7>, 'supported_features': <FanEntityFeature: 55>,
'translation_key': None, 'translation_key': None,
'unique_id': '00:00:00:00:00:00_1256851357_8', 'unique_id': '00:00:00:00:00:00_1256851357_8',
'unit_of_measurement': None, 'unit_of_measurement': None,
@ -9430,7 +9430,7 @@
'percentage_step': 1.0, 'percentage_step': 1.0,
'preset_mode': None, 'preset_mode': None,
'preset_modes': None, 'preset_modes': None,
'supported_features': <FanEntityFeature: 7>, 'supported_features': <FanEntityFeature: 55>,
}), }),
'entity_id': 'fan.living_room_fan', 'entity_id': 'fan.living_room_fan',
'state': 'off', 'state': 'off',
@ -9619,7 +9619,7 @@
'original_name': 'Living Room Fan', 'original_name': 'Living Room Fan',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
'supported_features': <FanEntityFeature: 7>, 'supported_features': <FanEntityFeature: 55>,
'translation_key': None, 'translation_key': None,
'unique_id': '00:00:00:00:00:00_1256851357_8', 'unique_id': '00:00:00:00:00:00_1256851357_8',
'unit_of_measurement': None, 'unit_of_measurement': None,
@ -9633,7 +9633,7 @@
'percentage_step': 1.0, 'percentage_step': 1.0,
'preset_mode': None, 'preset_mode': None,
'preset_modes': None, 'preset_modes': None,
'supported_features': <FanEntityFeature: 7>, 'supported_features': <FanEntityFeature: 55>,
}), }),
'entity_id': 'fan.living_room_fan', 'entity_id': 'fan.living_room_fan',
'state': 'off', 'state': 'off',
@ -9818,7 +9818,7 @@
'original_name': '89 Living Room', 'original_name': '89 Living Room',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
'supported_features': <FanEntityFeature: 3>, 'supported_features': <FanEntityFeature: 51>,
'translation_key': None, 'translation_key': None,
'unique_id': '00:00:00:00:00:00_1233851541_175', 'unique_id': '00:00:00:00:00:00_1233851541_175',
'unit_of_measurement': None, 'unit_of_measurement': None,
@ -9831,7 +9831,7 @@
'percentage_step': 33.333333333333336, 'percentage_step': 33.333333333333336,
'preset_mode': None, 'preset_mode': None,
'preset_modes': None, 'preset_modes': None,
'supported_features': <FanEntityFeature: 3>, 'supported_features': <FanEntityFeature: 51>,
}), }),
'entity_id': 'fan.89_living_room', 'entity_id': 'fan.89_living_room',
'state': 'on', 'state': 'on',
@ -14233,7 +14233,7 @@
'original_name': 'Caséta® Wireless Fan Speed Control', 'original_name': 'Caséta® Wireless Fan Speed Control',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
'supported_features': <FanEntityFeature: 1>, 'supported_features': <FanEntityFeature: 49>,
'translation_key': None, 'translation_key': None,
'unique_id': '00:00:00:00:00:00_21474836482_2', 'unique_id': '00:00:00:00:00:00_21474836482_2',
'unit_of_measurement': None, 'unit_of_measurement': None,
@ -14245,7 +14245,7 @@
'percentage_step': 25.0, 'percentage_step': 25.0,
'preset_mode': None, 'preset_mode': None,
'preset_modes': None, 'preset_modes': None,
'supported_features': <FanEntityFeature: 1>, 'supported_features': <FanEntityFeature: 49>,
}), }),
'entity_id': 'fan.caseta_r_wireless_fan_speed_control', 'entity_id': 'fan.caseta_r_wireless_fan_speed_control',
'state': 'off', 'state': 'off',
@ -17881,7 +17881,7 @@
'original_name': 'SIMPLEconnect Fan-06F674 Hunter Fan', 'original_name': 'SIMPLEconnect Fan-06F674 Hunter Fan',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
'supported_features': <FanEntityFeature: 5>, 'supported_features': <FanEntityFeature: 53>,
'translation_key': None, 'translation_key': None,
'unique_id': '00:00:00:00:00:00_1_8', 'unique_id': '00:00:00:00:00:00_1_8',
'unit_of_measurement': None, 'unit_of_measurement': None,
@ -17894,7 +17894,7 @@
'percentage_step': 25.0, 'percentage_step': 25.0,
'preset_mode': None, 'preset_mode': None,
'preset_modes': None, 'preset_modes': None,
'supported_features': <FanEntityFeature: 5>, 'supported_features': <FanEntityFeature: 53>,
}), }),
'entity_id': 'fan.simpleconnect_fan_06f674_hunter_fan', 'entity_id': 'fan.simpleconnect_fan_06f674_hunter_fan',
'state': 'off', 'state': 'off',

View file

@ -29,14 +29,22 @@ async def test_fan_add_feature_at_runtime(
fan_state = hass.states.get("fan.living_room_fan") fan_state = hass.states.get("fan.living_room_fan")
assert ( assert (
fan_state.attributes[ATTR_SUPPORTED_FEATURES] fan_state.attributes[ATTR_SUPPORTED_FEATURES]
is FanEntityFeature.SET_SPEED | FanEntityFeature.DIRECTION is FanEntityFeature.SET_SPEED
| FanEntityFeature.DIRECTION
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
) )
fan = entity_registry.async_get("fan.ceiling_fan") fan = entity_registry.async_get("fan.ceiling_fan")
assert fan.unique_id == "00:00:00:00:00:00_766313939_8" assert fan.unique_id == "00:00:00:00:00:00_766313939_8"
fan_state = hass.states.get("fan.ceiling_fan") fan_state = hass.states.get("fan.ceiling_fan")
assert fan_state.attributes[ATTR_SUPPORTED_FEATURES] is FanEntityFeature.SET_SPEED assert (
fan_state.attributes[ATTR_SUPPORTED_FEATURES]
is FanEntityFeature.SET_SPEED
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
)
# Now change the config to add oscillation # Now change the config to add oscillation
accessories = await setup_accessories_from_file( accessories = await setup_accessories_from_file(
@ -50,9 +58,16 @@ async def test_fan_add_feature_at_runtime(
is FanEntityFeature.SET_SPEED is FanEntityFeature.SET_SPEED
| FanEntityFeature.DIRECTION | FanEntityFeature.DIRECTION
| FanEntityFeature.OSCILLATE | FanEntityFeature.OSCILLATE
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
) )
fan_state = hass.states.get("fan.ceiling_fan") fan_state = hass.states.get("fan.ceiling_fan")
assert fan_state.attributes[ATTR_SUPPORTED_FEATURES] is FanEntityFeature.SET_SPEED assert (
fan_state.attributes[ATTR_SUPPORTED_FEATURES]
is FanEntityFeature.SET_SPEED
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
)
async def test_fan_remove_feature_at_runtime( async def test_fan_remove_feature_at_runtime(
@ -75,13 +90,20 @@ async def test_fan_remove_feature_at_runtime(
is FanEntityFeature.SET_SPEED is FanEntityFeature.SET_SPEED
| FanEntityFeature.DIRECTION | FanEntityFeature.DIRECTION
| FanEntityFeature.OSCILLATE | FanEntityFeature.OSCILLATE
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
) )
fan = entity_registry.async_get("fan.ceiling_fan") fan = entity_registry.async_get("fan.ceiling_fan")
assert fan.unique_id == "00:00:00:00:00:00_766313939_8" assert fan.unique_id == "00:00:00:00:00:00_766313939_8"
fan_state = hass.states.get("fan.ceiling_fan") fan_state = hass.states.get("fan.ceiling_fan")
assert fan_state.attributes[ATTR_SUPPORTED_FEATURES] is FanEntityFeature.SET_SPEED assert (
fan_state.attributes[ATTR_SUPPORTED_FEATURES]
is FanEntityFeature.SET_SPEED
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
)
# Now change the config to add oscillation # Now change the config to add oscillation
accessories = await setup_accessories_from_file( accessories = await setup_accessories_from_file(
@ -92,10 +114,18 @@ async def test_fan_remove_feature_at_runtime(
fan_state = hass.states.get("fan.living_room_fan") fan_state = hass.states.get("fan.living_room_fan")
assert ( assert (
fan_state.attributes[ATTR_SUPPORTED_FEATURES] fan_state.attributes[ATTR_SUPPORTED_FEATURES]
is FanEntityFeature.SET_SPEED | FanEntityFeature.DIRECTION is FanEntityFeature.SET_SPEED
| FanEntityFeature.DIRECTION
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
) )
fan_state = hass.states.get("fan.ceiling_fan") fan_state = hass.states.get("fan.ceiling_fan")
assert fan_state.attributes[ATTR_SUPPORTED_FEATURES] is FanEntityFeature.SET_SPEED assert (
fan_state.attributes[ATTR_SUPPORTED_FEATURES]
is FanEntityFeature.SET_SPEED
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
)
async def test_bridge_with_two_fans_one_removed( async def test_bridge_with_two_fans_one_removed(
@ -119,13 +149,20 @@ async def test_bridge_with_two_fans_one_removed(
is FanEntityFeature.SET_SPEED is FanEntityFeature.SET_SPEED
| FanEntityFeature.DIRECTION | FanEntityFeature.DIRECTION
| FanEntityFeature.OSCILLATE | FanEntityFeature.OSCILLATE
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
) )
fan = entity_registry.async_get("fan.ceiling_fan") fan = entity_registry.async_get("fan.ceiling_fan")
assert fan.unique_id == "00:00:00:00:00:00_766313939_8" assert fan.unique_id == "00:00:00:00:00:00_766313939_8"
fan_state = hass.states.get("fan.ceiling_fan") fan_state = hass.states.get("fan.ceiling_fan")
assert fan_state.attributes[ATTR_SUPPORTED_FEATURES] is FanEntityFeature.SET_SPEED assert (
fan_state.attributes[ATTR_SUPPORTED_FEATURES]
is FanEntityFeature.SET_SPEED
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
)
# Now change the config to remove one of the fans # Now change the config to remove one of the fans
accessories = await setup_accessories_from_file( accessories = await setup_accessories_from_file(
@ -141,6 +178,8 @@ async def test_bridge_with_two_fans_one_removed(
is FanEntityFeature.SET_SPEED is FanEntityFeature.SET_SPEED
| FanEntityFeature.DIRECTION | FanEntityFeature.DIRECTION
| FanEntityFeature.OSCILLATE | FanEntityFeature.OSCILLATE
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
) )
# The second fan should have been removed # The second fan should have been removed
assert not hass.states.get("fan.ceiling_fan") assert not hass.states.get("fan.ceiling_fan")

View file

@ -1590,7 +1590,7 @@ async def test_attributes(
} }
}, },
True, True,
fan.FanEntityFeature(0), fan.FanEntityFeature.TURN_OFF | fan.FanEntityFeature.TURN_ON,
None, None,
), ),
( (
@ -1605,7 +1605,9 @@ async def test_attributes(
} }
}, },
True, True,
fan.FanEntityFeature.OSCILLATE, fan.FanEntityFeature.OSCILLATE
| fan.FanEntityFeature.TURN_OFF
| fan.FanEntityFeature.TURN_ON,
None, None,
), ),
( (
@ -1620,7 +1622,9 @@ async def test_attributes(
} }
}, },
True, True,
fan.FanEntityFeature.SET_SPEED, fan.FanEntityFeature.SET_SPEED
| fan.FanEntityFeature.TURN_OFF
| fan.FanEntityFeature.TURN_ON,
None, None,
), ),
( (
@ -1651,7 +1655,9 @@ async def test_attributes(
} }
}, },
True, True,
fan.FanEntityFeature.PRESET_MODE, fan.FanEntityFeature.PRESET_MODE
| fan.FanEntityFeature.TURN_OFF
| fan.FanEntityFeature.TURN_ON,
None, None,
), ),
( (
@ -1667,7 +1673,9 @@ async def test_attributes(
} }
}, },
True, True,
fan.FanEntityFeature.PRESET_MODE, fan.FanEntityFeature.PRESET_MODE
| fan.FanEntityFeature.TURN_OFF
| fan.FanEntityFeature.TURN_ON,
None, None,
), ),
( (
@ -1682,7 +1690,9 @@ async def test_attributes(
} }
}, },
True, True,
fan.FanEntityFeature.SET_SPEED, fan.FanEntityFeature.SET_SPEED
| fan.FanEntityFeature.TURN_OFF
| fan.FanEntityFeature.TURN_ON,
None, None,
), ),
( (
@ -1698,7 +1708,10 @@ async def test_attributes(
} }
}, },
True, True,
fan.FanEntityFeature.OSCILLATE | fan.FanEntityFeature.SET_SPEED, fan.FanEntityFeature.OSCILLATE
| fan.FanEntityFeature.SET_SPEED
| fan.FanEntityFeature.TURN_OFF
| fan.FanEntityFeature.TURN_ON,
None, None,
), ),
( (
@ -1714,7 +1727,9 @@ async def test_attributes(
} }
}, },
True, True,
fan.FanEntityFeature.PRESET_MODE, fan.FanEntityFeature.PRESET_MODE
| fan.FanEntityFeature.TURN_OFF
| fan.FanEntityFeature.TURN_ON,
None, None,
), ),
( (
@ -1730,7 +1745,9 @@ async def test_attributes(
} }
}, },
True, True,
fan.FanEntityFeature.PRESET_MODE, fan.FanEntityFeature.PRESET_MODE
| fan.FanEntityFeature.TURN_OFF
| fan.FanEntityFeature.TURN_ON,
None, None,
), ),
( (
@ -1747,7 +1764,10 @@ async def test_attributes(
} }
}, },
True, True,
fan.FanEntityFeature.PRESET_MODE | fan.FanEntityFeature.OSCILLATE, fan.FanEntityFeature.PRESET_MODE
| fan.FanEntityFeature.OSCILLATE
| fan.FanEntityFeature.TURN_OFF
| fan.FanEntityFeature.TURN_ON,
None, None,
), ),
( (
@ -1764,7 +1784,9 @@ async def test_attributes(
} }
}, },
True, True,
fan.FanEntityFeature.SET_SPEED, fan.FanEntityFeature.SET_SPEED
| fan.FanEntityFeature.TURN_OFF
| fan.FanEntityFeature.TURN_ON,
None, None,
), ),
( (
@ -1831,7 +1853,9 @@ async def test_attributes(
} }
}, },
True, True,
fan.FanEntityFeature.PRESET_MODE, fan.FanEntityFeature.PRESET_MODE
| fan.FanEntityFeature.TURN_OFF
| fan.FanEntityFeature.TURN_ON,
"some error", "some error",
), ),
( (
@ -1846,7 +1870,9 @@ async def test_attributes(
} }
}, },
True, True,
fan.FanEntityFeature.DIRECTION, fan.FanEntityFeature.DIRECTION
| fan.FanEntityFeature.TURN_OFF
| fan.FanEntityFeature.TURN_ON,
"some error", "some error",
), ),
], ],

View file

@ -39,7 +39,12 @@ async def test_entity_state(hass: HomeAssistant, device_factory) -> None:
# Dimmer 1 # Dimmer 1
state = hass.states.get("fan.fan_1") state = hass.states.get("fan.fan_1")
assert state.state == "on" assert state.state == "on"
assert state.attributes[ATTR_SUPPORTED_FEATURES] == FanEntityFeature.SET_SPEED assert (
state.attributes[ATTR_SUPPORTED_FEATURES]
== FanEntityFeature.SET_SPEED
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
)
assert state.attributes[ATTR_PERCENTAGE] == 66 assert state.attributes[ATTR_PERCENTAGE] == 66
@ -100,7 +105,12 @@ async def test_setup_mode_capability(hass: HomeAssistant, device_factory) -> Non
# Assert # Assert
state = hass.states.get("fan.fan_1") state = hass.states.get("fan.fan_1")
assert state is not None assert state is not None
assert state.attributes[ATTR_SUPPORTED_FEATURES] == FanEntityFeature.PRESET_MODE assert (
state.attributes[ATTR_SUPPORTED_FEATURES]
== FanEntityFeature.PRESET_MODE
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
)
assert state.attributes[ATTR_PRESET_MODE] == "high" assert state.attributes[ATTR_PRESET_MODE] == "high"
assert state.attributes[ATTR_PRESET_MODES] == ["high", "low", "medium"] assert state.attributes[ATTR_PRESET_MODES] == ["high", "low", "medium"]
@ -122,7 +132,12 @@ async def test_setup_speed_capability(hass: HomeAssistant, device_factory) -> No
# Assert # Assert
state = hass.states.get("fan.fan_1") state = hass.states.get("fan.fan_1")
assert state is not None assert state is not None
assert state.attributes[ATTR_SUPPORTED_FEATURES] == FanEntityFeature.SET_SPEED assert (
state.attributes[ATTR_SUPPORTED_FEATURES]
== FanEntityFeature.SET_SPEED
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
)
assert state.attributes[ATTR_PERCENTAGE] == 66 assert state.attributes[ATTR_PERCENTAGE] == 66
@ -151,7 +166,10 @@ async def test_setup_both_capabilities(hass: HomeAssistant, device_factory) -> N
assert state is not None assert state is not None
assert ( assert (
state.attributes[ATTR_SUPPORTED_FEATURES] state.attributes[ATTR_SUPPORTED_FEATURES]
== FanEntityFeature.SET_SPEED | FanEntityFeature.PRESET_MODE == FanEntityFeature.SET_SPEED
| FanEntityFeature.PRESET_MODE
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
) )
assert state.attributes[ATTR_PERCENTAGE] == 66 assert state.attributes[ATTR_PERCENTAGE] == 66
assert state.attributes[ATTR_PRESET_MODE] == "high" assert state.attributes[ATTR_PRESET_MODE] == "high"

View file

@ -44,7 +44,7 @@ async def test_default_state(hass: HomeAssistant) -> None:
state = hass.states.get("fan.wind_machine") state = hass.states.get("fan.wind_machine")
assert state is not None assert state is not None
assert state.state == "unavailable" assert state.state == "unavailable"
assert state.attributes["supported_features"] == 0 assert state.attributes["supported_features"] == 48
async def test_service_calls(hass: HomeAssistant) -> None: async def test_service_calls(hass: HomeAssistant) -> None:

View file

@ -61,7 +61,12 @@ async def test_controlling_state_via_mqtt(
state = hass.states.get("fan.tasmota") state = hass.states.get("fan.tasmota")
assert state.state == STATE_OFF assert state.state == STATE_OFF
assert state.attributes["percentage"] is None assert state.attributes["percentage"] is None
assert state.attributes["supported_features"] == fan.FanEntityFeature.SET_SPEED assert (
state.attributes["supported_features"]
== fan.FanEntityFeature.SET_SPEED
| fan.FanEntityFeature.TURN_OFF
| fan.FanEntityFeature.TURN_ON
)
assert not state.attributes.get(ATTR_ASSUMED_STATE) assert not state.attributes.get(ATTR_ASSUMED_STATE)
async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/STATE", '{"FanSpeed":1}') async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/STATE", '{"FanSpeed":1}')

View file

@ -28,7 +28,7 @@
'original_name': None, 'original_name': None,
'platform': 'tplink', 'platform': 'tplink',
'previous_unique_id': None, 'previous_unique_id': None,
'supported_features': <FanEntityFeature: 1>, 'supported_features': <FanEntityFeature: 49>,
'translation_key': None, 'translation_key': None,
'unique_id': '123456789ABCDEFGH', 'unique_id': '123456789ABCDEFGH',
'unit_of_measurement': None, 'unit_of_measurement': None,
@ -42,7 +42,7 @@
'percentage_step': 25.0, 'percentage_step': 25.0,
'preset_mode': None, 'preset_mode': None,
'preset_modes': None, 'preset_modes': None,
'supported_features': <FanEntityFeature: 1>, 'supported_features': <FanEntityFeature: 49>,
}), }),
'context': <ANY>, 'context': <ANY>,
'entity_id': 'fan.my_device', 'entity_id': 'fan.my_device',
@ -81,7 +81,7 @@
'original_name': 'my_fan_0', 'original_name': 'my_fan_0',
'platform': 'tplink', 'platform': 'tplink',
'previous_unique_id': None, 'previous_unique_id': None,
'supported_features': <FanEntityFeature: 1>, 'supported_features': <FanEntityFeature: 49>,
'translation_key': None, 'translation_key': None,
'unique_id': '123456789ABCDEFGH00', 'unique_id': '123456789ABCDEFGH00',
'unit_of_measurement': None, 'unit_of_measurement': None,
@ -95,7 +95,7 @@
'percentage_step': 25.0, 'percentage_step': 25.0,
'preset_mode': None, 'preset_mode': None,
'preset_modes': None, 'preset_modes': None,
'supported_features': <FanEntityFeature: 1>, 'supported_features': <FanEntityFeature: 49>,
}), }),
'context': <ANY>, 'context': <ANY>,
'entity_id': 'fan.my_device_my_fan_0', 'entity_id': 'fan.my_device_my_fan_0',
@ -134,7 +134,7 @@
'original_name': 'my_fan_1', 'original_name': 'my_fan_1',
'platform': 'tplink', 'platform': 'tplink',
'previous_unique_id': None, 'previous_unique_id': None,
'supported_features': <FanEntityFeature: 1>, 'supported_features': <FanEntityFeature: 49>,
'translation_key': None, 'translation_key': None,
'unique_id': '123456789ABCDEFGH01', 'unique_id': '123456789ABCDEFGH01',
'unit_of_measurement': None, 'unit_of_measurement': None,
@ -148,7 +148,7 @@
'percentage_step': 25.0, 'percentage_step': 25.0,
'preset_mode': None, 'preset_mode': None,
'preset_modes': None, 'preset_modes': None,
'supported_features': <FanEntityFeature: 1>, 'supported_features': <FanEntityFeature: 49>,
}), }),
'context': <ANY>, 'context': <ANY>,
'entity_id': 'fan.my_device_my_fan_1', 'entity_id': 'fan.my_device_my_fan_1',

View file

@ -52,7 +52,7 @@ async def test_fan_available(
assert state.attributes[ATTR_PERCENTAGE_STEP] == pytest.approx(2.040816) assert state.attributes[ATTR_PERCENTAGE_STEP] == pytest.approx(2.040816)
assert state.attributes[ATTR_PRESET_MODES] == ["Auto"] assert state.attributes[ATTR_PRESET_MODES] == ["Auto"]
assert state.attributes[ATTR_PRESET_MODE] is None assert state.attributes[ATTR_PRESET_MODE] is None
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 9 assert state.attributes[ATTR_SUPPORTED_FEATURES] == 57
await command_store.trigger_observe_callback( await command_store.trigger_observe_callback(
hass, device, {ATTR_REACHABLE_STATE: 0} hass, device, {ATTR_REACHABLE_STATE: 0}
@ -172,7 +172,7 @@ async def test_services(
assert state.attributes[ATTR_PERCENTAGE_STEP] == pytest.approx(2.040816) assert state.attributes[ATTR_PERCENTAGE_STEP] == pytest.approx(2.040816)
assert state.attributes[ATTR_PRESET_MODES] == ["Auto"] assert state.attributes[ATTR_PRESET_MODES] == ["Auto"]
assert state.attributes[ATTR_PRESET_MODE] is None assert state.attributes[ATTR_PRESET_MODE] is None
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 9 assert state.attributes[ATTR_SUPPORTED_FEATURES] == 57
await hass.services.async_call( await hass.services.async_call(
FAN_DOMAIN, FAN_DOMAIN,

View file

@ -203,7 +203,7 @@
'auto', 'auto',
'sleep', 'sleep',
]), ]),
'supported_features': 9, 'supported_features': 57,
}), }),
'entity_id': 'fan.fan', 'entity_id': 'fan.fan',
'last_changed': str, 'last_changed': str,

View file

@ -66,7 +66,7 @@
'original_name': None, 'original_name': None,
'platform': 'vesync', 'platform': 'vesync',
'previous_unique_id': None, 'previous_unique_id': None,
'supported_features': <FanEntityFeature: 9>, 'supported_features': <FanEntityFeature: 57>,
'translation_key': None, 'translation_key': None,
'unique_id': 'air-purifier', 'unique_id': 'air-purifier',
'unit_of_measurement': None, 'unit_of_measurement': None,
@ -81,7 +81,7 @@
'auto', 'auto',
'sleep', 'sleep',
]), ]),
'supported_features': <FanEntityFeature: 9>, 'supported_features': <FanEntityFeature: 57>,
}), }),
'context': <ANY>, 'context': <ANY>,
'entity_id': 'fan.air_purifier_131s', 'entity_id': 'fan.air_purifier_131s',
@ -157,7 +157,7 @@
'original_name': None, 'original_name': None,
'platform': 'vesync', 'platform': 'vesync',
'previous_unique_id': None, 'previous_unique_id': None,
'supported_features': <FanEntityFeature: 9>, 'supported_features': <FanEntityFeature: 57>,
'translation_key': None, 'translation_key': None,
'unique_id': 'asd_sdfKIHG7IJHGwJGJ7GJ_ag5h3G55', 'unique_id': 'asd_sdfKIHG7IJHGwJGJ7GJ_ag5h3G55',
'unit_of_measurement': None, 'unit_of_measurement': None,
@ -178,7 +178,7 @@
'sleep', 'sleep',
]), ]),
'screen_status': True, 'screen_status': True,
'supported_features': <FanEntityFeature: 9>, 'supported_features': <FanEntityFeature: 57>,
}), }),
'context': <ANY>, 'context': <ANY>,
'entity_id': 'fan.air_purifier_200s', 'entity_id': 'fan.air_purifier_200s',
@ -255,7 +255,7 @@
'original_name': None, 'original_name': None,
'platform': 'vesync', 'platform': 'vesync',
'previous_unique_id': None, 'previous_unique_id': None,
'supported_features': <FanEntityFeature: 9>, 'supported_features': <FanEntityFeature: 57>,
'translation_key': None, 'translation_key': None,
'unique_id': '400s-purifier', 'unique_id': '400s-purifier',
'unit_of_measurement': None, 'unit_of_measurement': None,
@ -277,7 +277,7 @@
'sleep', 'sleep',
]), ]),
'screen_status': True, 'screen_status': True,
'supported_features': <FanEntityFeature: 9>, 'supported_features': <FanEntityFeature: 57>,
}), }),
'context': <ANY>, 'context': <ANY>,
'entity_id': 'fan.air_purifier_400s', 'entity_id': 'fan.air_purifier_400s',
@ -354,7 +354,7 @@
'original_name': None, 'original_name': None,
'platform': 'vesync', 'platform': 'vesync',
'previous_unique_id': None, 'previous_unique_id': None,
'supported_features': <FanEntityFeature: 9>, 'supported_features': <FanEntityFeature: 57>,
'translation_key': None, 'translation_key': None,
'unique_id': '600s-purifier', 'unique_id': '600s-purifier',
'unit_of_measurement': None, 'unit_of_measurement': None,
@ -376,7 +376,7 @@
'sleep', 'sleep',
]), ]),
'screen_status': True, 'screen_status': True,
'supported_features': <FanEntityFeature: 9>, 'supported_features': <FanEntityFeature: 57>,
}), }),
'context': <ANY>, 'context': <ANY>,
'entity_id': 'fan.air_purifier_600s', 'entity_id': 'fan.air_purifier_600s',

View file

@ -653,7 +653,12 @@ async def test_thermostat_fan(
assert state.state == STATE_ON assert state.state == STATE_ON
assert state.attributes.get(ATTR_FAN_STATE) == "Idle / off" assert state.attributes.get(ATTR_FAN_STATE) == "Idle / off"
assert state.attributes.get(ATTR_PRESET_MODE) == "Auto low" assert state.attributes.get(ATTR_PRESET_MODE) == "Auto low"
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == FanEntityFeature.PRESET_MODE assert (
state.attributes.get(ATTR_SUPPORTED_FEATURES)
== FanEntityFeature.PRESET_MODE
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
)
# Test setting preset mode # Test setting preset mode
await hass.services.async_call( await hass.services.async_call(