From b617d2bab030e112dc88851ec46bb739fdac3b54 Mon Sep 17 00:00:00 2001 From: Jeef Date: Sun, 25 Sep 2022 21:24:46 -0600 Subject: [PATCH] IntelliFire Fan Support (#74181) Co-authored-by: J. Nick Koston Co-authored-by: Paulus Schoutsen --- .coveragerc | 1 + .../components/intellifire/__init__.py | 8 +- homeassistant/components/intellifire/fan.py | 130 ++++++++++++++++++ 3 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/intellifire/fan.py diff --git a/.coveragerc b/.coveragerc index 44f8a54fe7a..da6ae8b4cc1 100644 --- a/.coveragerc +++ b/.coveragerc @@ -574,6 +574,7 @@ omit = homeassistant/components/intellifire/climate.py homeassistant/components/intellifire/coordinator.py homeassistant/components/intellifire/entity.py + homeassistant/components/intellifire/fan.py homeassistant/components/intellifire/sensor.py homeassistant/components/intellifire/switch.py homeassistant/components/intesishome/* diff --git a/homeassistant/components/intellifire/__init__.py b/homeassistant/components/intellifire/__init__.py index f5b6085781b..020136f078c 100644 --- a/homeassistant/components/intellifire/__init__.py +++ b/homeassistant/components/intellifire/__init__.py @@ -20,7 +20,13 @@ from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from .const import CONF_USER_ID, DOMAIN, LOGGER from .coordinator import IntellifireDataUpdateCoordinator -PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR, Platform.CLIMATE, Platform.SWITCH] +PLATFORMS = [ + Platform.BINARY_SENSOR, + Platform.CLIMATE, + Platform.FAN, + Platform.SENSOR, + Platform.SWITCH, +] async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: diff --git a/homeassistant/components/intellifire/fan.py b/homeassistant/components/intellifire/fan.py new file mode 100644 index 00000000000..d37bef2189a --- /dev/null +++ b/homeassistant/components/intellifire/fan.py @@ -0,0 +1,130 @@ +"""Fan definition for Intellifire.""" +from __future__ import annotations + +from collections.abc import Awaitable, Callable +from dataclasses import dataclass +import math +from typing import Any + +from intellifire4py import IntellifireControlAsync, IntellifirePollData + +from homeassistant.components.fan import ( + FanEntity, + FanEntityDescription, + FanEntityFeature, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.util.percentage import ( + percentage_to_ranged_value, + ranged_value_to_percentage, +) + +from .const import DOMAIN, LOGGER +from .coordinator import IntellifireDataUpdateCoordinator +from .entity import IntellifireEntity + + +@dataclass +class IntellifireFanRequiredKeysMixin: + """Required keys for fan entity.""" + + set_fn: Callable[[IntellifireControlAsync, int], Awaitable] + value_fn: Callable[[IntellifirePollData], bool] + speed_range: tuple[int, int] + + +@dataclass +class IntellifireFanEntityDescription( + FanEntityDescription, IntellifireFanRequiredKeysMixin +): + """Describes a fan entity.""" + + +INTELLIFIRE_FANS: tuple[IntellifireFanEntityDescription, ...] = ( + IntellifireFanEntityDescription( + key="fan", + name="Fan", + has_entity_name=True, + set_fn=lambda control_api, speed: control_api.set_fan_speed(speed=speed), + value_fn=lambda data: data.fanspeed, + speed_range=(1, 4), + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the fans.""" + coordinator: IntellifireDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + + if coordinator.data.has_fan: + async_add_entities( + IntellifireFan(coordinator=coordinator, description=description) + for description in INTELLIFIRE_FANS + ) + return + LOGGER.debug("Disabling Fan - IntelliFire device does not appear to have one") + + +class IntellifireFan(IntellifireEntity, FanEntity): + """This is Fan entity for the fireplace.""" + + entity_description: IntellifireFanEntityDescription + _attr_supported_features = FanEntityFeature.SET_SPEED + + @property + def is_on(self) -> bool: + """Return on or off.""" + return self.entity_description.value_fn(self.coordinator.read_api.data) >= 1 + + @property + def percentage(self) -> int | None: + """Return fan percentage.""" + return ranged_value_to_percentage( + self.entity_description.speed_range, self.coordinator.read_api.data.fanspeed + ) + + @property + def speed_count(self) -> int: + """Count of supported speeds.""" + return self.entity_description.speed_range[1] + + async def async_set_percentage(self, percentage: int) -> None: + """Set the speed percentage of the fan.""" + # Calculate percentage steps + LOGGER.debug("Setting Fan Speed %s", percentage) + + int_value = math.ceil( + percentage_to_ranged_value(self.entity_description.speed_range, percentage) + ) + await self.entity_description.set_fn(self.coordinator.control_api, int_value) + await self.coordinator.async_request_refresh() + + async def async_turn_on( + self, + percentage: int | None = None, + preset_mode: str | None = None, + **kwargs: Any, + ) -> None: + """Turn on the fan.""" + if percentage: + int_value = math.ceil( + percentage_to_ranged_value( + self.entity_description.speed_range, percentage + ) + ) + else: + int_value = 1 + await self.entity_description.set_fn(self.coordinator.control_api, int_value) + await self.coordinator.async_request_refresh() + + async def async_turn_off(self, **kwargs: Any) -> None: + """Turn off the fan.""" + self.coordinator.control_api.fan_off() + await self.entity_description.set_fn(self.coordinator.control_api, 0) + await self.coordinator.async_request_refresh()