Files

203 lines
6.3 KiB
Python

"""Platform for Miele fan entity."""
from dataclasses import dataclass
import logging
import math
from typing import Any, Final, override
from aiohttp import ClientResponseError
from homeassistant.components.fan import (
FanEntity,
FanEntityDescription,
FanEntityFeature,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.util.percentage import (
percentage_to_ranged_value,
ranged_value_to_percentage,
)
from homeassistant.util.scaling import int_states_in_range
from .const import DOMAIN, POWER_OFF, POWER_ON, VENTILATION_STEP, MieleAppliance
from .coordinator import MieleConfigEntry, MieleDataUpdateCoordinator
from .entity import MieleEntity
PARALLEL_UPDATES = 1
_LOGGER = logging.getLogger(__name__)
SPEED_RANGE = (1, 4)
@dataclass(frozen=True, kw_only=True)
class MieleFanDefinition:
"""Class for defining fan entities."""
types: tuple[MieleAppliance, ...]
description: FanEntityDescription
FAN_TYPES: Final[tuple[MieleFanDefinition, ...]] = (
MieleFanDefinition(
types=(MieleAppliance.HOOD,),
description=FanEntityDescription(
key="fan",
translation_key="fan",
),
),
MieleFanDefinition(
types=(MieleAppliance.HOB_INDUCT_EXTR,),
description=FanEntityDescription(
key="fan_readonly",
translation_key="fan",
),
),
)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: MieleConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the fan platform."""
coordinator = config_entry.runtime_data.coordinator
added_devices: set[str] = set()
def _async_add_new_devices() -> None:
nonlocal added_devices
new_devices_set, current_devices = coordinator.async_add_devices(added_devices)
added_devices = current_devices
async_add_entities(
MieleFan(coordinator, device_id, definition.description)
for device_id, device in coordinator.data.devices.items()
for definition in FAN_TYPES
if device_id in new_devices_set and device.device_type in definition.types
)
config_entry.async_on_unload(coordinator.async_add_listener(_async_add_new_devices))
_async_add_new_devices()
class MieleFan(MieleEntity, FanEntity):
"""Representation of a Fan."""
entity_description: FanEntityDescription
def __init__(
self,
coordinator: MieleDataUpdateCoordinator,
device_id: str,
description: FanEntityDescription,
) -> None:
"""Initialize the fan."""
self._attr_supported_features: FanEntityFeature = (
FanEntityFeature(0)
if description.key == "fan_readonly"
else FanEntityFeature.SET_SPEED
| FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON
)
super().__init__(coordinator, device_id, description)
@property
@override
def is_on(self) -> bool:
"""Return current on/off state."""
return (
self.device.state_ventilation_step is not None
and self.device.state_ventilation_step > 0
)
@property
@override
def speed_count(self) -> int:
"""Return the number of speeds the fan supports."""
return int_states_in_range(SPEED_RANGE)
@property
@override
def percentage(self) -> int | None:
"""Return the current speed percentage."""
return ranged_value_to_percentage(
SPEED_RANGE,
(self.device.state_ventilation_step or 0),
)
@override
async def async_set_percentage(self, percentage: int) -> None:
"""Set the speed percentage of the fan."""
_LOGGER.debug("Set_percentage: %s", percentage)
ventilation_step = math.ceil(
percentage_to_ranged_value(SPEED_RANGE, percentage)
)
_LOGGER.debug("Calc ventilation_step: %s", ventilation_step)
if ventilation_step == 0:
await self.async_turn_off()
elif ventilation_step == self.device.state_ventilation_step:
return
else:
try:
await self.api.send_action(
self._device_id, {VENTILATION_STEP: ventilation_step}
)
except ClientResponseError as err:
_LOGGER.debug("Error setting fan state for %s: %s", self.entity_id, err)
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="set_state_error",
translation_placeholders={
"entity": self.entity_id,
},
) from err
self.device.state_ventilation_step = ventilation_step
self.async_write_ha_state()
@override
async def async_turn_on(
self,
percentage: int | None = None,
preset_mode: str | None = None,
**kwargs: Any,
) -> None:
"""Turn on the fan."""
_LOGGER.debug(
"Turn_on -> percentage: %s, preset_mode: %s", percentage, preset_mode
)
try:
await self.api.send_action(self._device_id, {POWER_ON: True})
except ClientResponseError as ex:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="set_state_error",
translation_placeholders={
"entity": self.entity_id,
},
) from ex
if percentage is not None:
await self.async_set_percentage(percentage)
return
@override
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the fan off."""
try:
await self.api.send_action(self._device_id, {POWER_OFF: True})
except ClientResponseError as ex:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="set_state_error",
translation_placeholders={
"entity": self.entity_id,
},
) from ex
self.device.state_ventilation_step = 0
self.async_write_ha_state()