diff --git a/.coveragerc b/.coveragerc index 44fe8c41b52..caa2247245b 100644 --- a/.coveragerc +++ b/.coveragerc @@ -978,7 +978,9 @@ omit = homeassistant/components/renson/__init__.py homeassistant/components/renson/const.py homeassistant/components/renson/entity.py + homeassistant/components/renson/fan.py homeassistant/components/renson/sensor.py + homeassistant/components/renson/binary_sensor.py homeassistant/components/raspyrfm/* homeassistant/components/recollect_waste/sensor.py homeassistant/components/recorder/repack.py diff --git a/homeassistant/components/renson/__init__.py b/homeassistant/components/renson/__init__.py index 083f583fced..59b5e5f0e14 100644 --- a/homeassistant/components/renson/__init__.py +++ b/homeassistant/components/renson/__init__.py @@ -21,6 +21,7 @@ _LOGGER = logging.getLogger(__name__) PLATFORMS = [ Platform.SENSOR, Platform.BINARY_SENSOR, + Platform.FAN, ] diff --git a/homeassistant/components/renson/fan.py b/homeassistant/components/renson/fan.py new file mode 100644 index 00000000000..b282e80632b --- /dev/null +++ b/homeassistant/components/renson/fan.py @@ -0,0 +1,125 @@ +"""Platform to control a Renson ventilation unit.""" +from __future__ import annotations + +import logging +import math +from typing import Any + +from renson_endura_delta.field_enum import CURRENT_LEVEL_FIELD, DataType +from renson_endura_delta.renson import Level, RensonVentilation + +from homeassistant.components.fan import FanEntity, FanEntityFeature +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.util.percentage import ( + int_states_in_range, + percentage_to_ranged_value, + ranged_value_to_percentage, +) + +from . import RensonCoordinator +from .const import DOMAIN +from .entity import RensonEntity + +_LOGGER = logging.getLogger(__name__) + +CMD_MAPPING = { + 0: Level.HOLIDAY, + 1: Level.LEVEL1, + 2: Level.LEVEL2, + 3: Level.LEVEL3, + 4: Level.LEVEL4, +} + +SPEED_MAPPING = { + Level.OFF.value: 0, + Level.HOLIDAY.value: 0, + Level.LEVEL1.value: 1, + Level.LEVEL2.value: 2, + Level.LEVEL3.value: 3, + Level.LEVEL4.value: 4, +} + + +SPEED_RANGE: tuple[float, float] = (1, 4) + + +class RensonFan(RensonEntity, FanEntity): + """Representation of the Renson fan platform.""" + + _attr_icon = "mdi:air-conditioner" + _attr_name = "Fan" + _attr_supported_features = FanEntityFeature.SET_SPEED + current_speed: int | None = None + + def __init__(self, api: RensonVentilation, coordinator: RensonCoordinator) -> None: + """Initialize the Renson fan.""" + super().__init__("fan", api, coordinator) + + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + level = self.api.parse_value( + self.api.get_field_value(self.coordinator.data, CURRENT_LEVEL_FIELD.name), + DataType.LEVEL, + ) + + self.current_speed = SPEED_MAPPING[level] + + self.async_write_ha_state() + + @property + def percentage(self) -> int | None: + """Return the current speed percentage.""" + if self.current_speed is None: + return None + return ranged_value_to_percentage(SPEED_RANGE, self.current_speed) + + @property + def speed_count(self) -> int: + """Return the number of speeds the fan supports.""" + return int_states_in_range(SPEED_RANGE) + + async def async_turn_on( + self, + percentage: int | None = None, + preset_mode: str | None = None, + **kwargs: Any, + ) -> None: + """Turn on the fan.""" + if percentage is None: + percentage = 1 + + await self.async_set_percentage(percentage) + + async def async_turn_off(self, **kwargs: Any) -> None: + """Turn off the fan (to away).""" + await self.async_set_percentage(0) + + async def async_set_percentage(self, percentage: int) -> None: + """Set fan speed percentage.""" + _LOGGER.debug("Changing fan speed percentage to %s", percentage) + + if percentage == 0: + cmd = Level.HOLIDAY + else: + speed = math.ceil(percentage_to_ranged_value(SPEED_RANGE, percentage)) + cmd = CMD_MAPPING[speed] + + await self.hass.async_add_executor_job(self.api.set_manual_level, cmd) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the Renson fan platform.""" + + api: RensonVentilation = hass.data[DOMAIN][config_entry.entry_id].api + coordinator: RensonCoordinator = hass.data[DOMAIN][ + config_entry.entry_id + ].coordinator + + async_add_entities([RensonFan(api, coordinator)])