* add ventilation program & mode * add ventilation device * Update climate.py * Update climate.py * Update climate.py * Update climate.py * Update climate.py * Update const.py * Create fan.py * Update fan.py * Update types.py * add test case * add translation key * use translation key * update snapshot * fix ruff findings * fix ruff findings * add log messages to setter * adjust test case * reset climate entity * do not display speed if not in permanent mode * update snapshot * update test cases * add comment * mark fan as always on * prevent turning off device * allow to set permanent mode * make speed_count static * add debug outputs * add preset state translations * allow permanent mode * update snapshot * add test case * load programs only on init * comment on ventilation modes * adjust test cases * add exception message * ignore test coverage on fan.py * Update test_fan.py * simplify * Apply suggestions from code review * remove tests * remove extra state attributes * fix leftover * add missing labels * adjust label * change state keys * use _attr_preset_modes * fix ruff findings * fix attribute access * fix from_ha_mode * fix ruff findings * fix mypy findings * simplify * format * fix typo * fix ruff finding * Apply suggestions from code review * change fan mode handling * add test cases * remove turn_off * Apply suggestions from code review Co-authored-by: Erik Montnemery <erik@montnemery.com> * Apply suggestions from code review * Update fan.py --------- Co-authored-by: Erik Montnemery <erik@montnemery.com>
124 lines
4.3 KiB
Python
124 lines
4.3 KiB
Python
"""Viessmann ViCare ventilation device."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from contextlib import suppress
|
|
import logging
|
|
|
|
from PyViCare.PyViCareDevice import Device as PyViCareDevice
|
|
from PyViCare.PyViCareDeviceConfig import PyViCareDeviceConfig
|
|
from PyViCare.PyViCareUtils import (
|
|
PyViCareInvalidDataError,
|
|
PyViCareNotSupportedFeatureError,
|
|
PyViCareRateLimitError,
|
|
)
|
|
from PyViCare.PyViCareVentilationDevice import (
|
|
VentilationDevice as PyViCareVentilationDevice,
|
|
)
|
|
from requests.exceptions import ConnectionError as RequestConnectionError
|
|
|
|
from homeassistant.components.fan import FanEntity, FanEntityFeature
|
|
from homeassistant.config_entries import ConfigEntry
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|
from homeassistant.util.percentage import (
|
|
ordered_list_item_to_percentage,
|
|
percentage_to_ordered_list_item,
|
|
)
|
|
|
|
from .const import DEVICE_LIST, DOMAIN
|
|
from .entity import ViCareEntity
|
|
from .types import VentilationMode, VentilationProgram
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
ORDERED_NAMED_FAN_SPEEDS = [
|
|
VentilationProgram.LEVEL_ONE,
|
|
VentilationProgram.LEVEL_TWO,
|
|
VentilationProgram.LEVEL_THREE,
|
|
VentilationProgram.LEVEL_FOUR,
|
|
]
|
|
|
|
|
|
async def async_setup_entry(
|
|
hass: HomeAssistant,
|
|
config_entry: ConfigEntry,
|
|
async_add_entities: AddEntitiesCallback,
|
|
) -> None:
|
|
"""Set up the ViCare fan platform."""
|
|
|
|
device_list = hass.data[DOMAIN][config_entry.entry_id][DEVICE_LIST]
|
|
|
|
async_add_entities(
|
|
[
|
|
ViCareFan(device.config, device.api)
|
|
for device in device_list
|
|
if isinstance(device.api, PyViCareVentilationDevice)
|
|
]
|
|
)
|
|
|
|
|
|
class ViCareFan(ViCareEntity, FanEntity):
|
|
"""Representation of the ViCare ventilation device."""
|
|
|
|
_attr_preset_modes = list[str](
|
|
[
|
|
VentilationMode.PERMANENT,
|
|
VentilationMode.VENTILATION,
|
|
VentilationMode.SENSOR_DRIVEN,
|
|
VentilationMode.SENSOR_OVERRIDE,
|
|
]
|
|
)
|
|
_attr_speed_count = len(ORDERED_NAMED_FAN_SPEEDS)
|
|
_attr_supported_features = FanEntityFeature.SET_SPEED | FanEntityFeature.PRESET_MODE
|
|
_attr_translation_key = "ventilation"
|
|
_enable_turn_on_off_backwards_compatibility = False
|
|
|
|
def __init__(
|
|
self,
|
|
device_config: PyViCareDeviceConfig,
|
|
device: PyViCareDevice,
|
|
) -> None:
|
|
"""Initialize the fan entity."""
|
|
super().__init__(device_config, device, self._attr_translation_key)
|
|
|
|
def update(self) -> None:
|
|
"""Update state of fan."""
|
|
try:
|
|
with suppress(PyViCareNotSupportedFeatureError):
|
|
self._attr_preset_mode = VentilationMode.from_vicare_mode(
|
|
self._api.getActiveMode()
|
|
)
|
|
with suppress(PyViCareNotSupportedFeatureError):
|
|
self._attr_percentage = ordered_list_item_to_percentage(
|
|
ORDERED_NAMED_FAN_SPEEDS, self._api.getActiveProgram()
|
|
)
|
|
except RequestConnectionError:
|
|
_LOGGER.error("Unable to retrieve data from ViCare server")
|
|
except ValueError:
|
|
_LOGGER.error("Unable to decode data from ViCare server")
|
|
except PyViCareRateLimitError as limit_exception:
|
|
_LOGGER.error("Vicare API rate limit exceeded: %s", limit_exception)
|
|
except PyViCareInvalidDataError as invalid_data_exception:
|
|
_LOGGER.error("Invalid data from Vicare server: %s", invalid_data_exception)
|
|
|
|
@property
|
|
def is_on(self) -> bool | None:
|
|
"""Return true if the entity is on."""
|
|
# Viessmann ventilation unit cannot be turned off
|
|
return True
|
|
|
|
def set_percentage(self, percentage: int) -> None:
|
|
"""Set the speed of the fan, as a percentage."""
|
|
if self._attr_preset_mode != str(VentilationMode.PERMANENT):
|
|
self.set_preset_mode(VentilationMode.PERMANENT)
|
|
|
|
level = percentage_to_ordered_list_item(ORDERED_NAMED_FAN_SPEEDS, percentage)
|
|
_LOGGER.debug("changing ventilation level to %s", level)
|
|
self._api.setPermanentLevel(level)
|
|
|
|
def set_preset_mode(self, preset_mode: str) -> None:
|
|
"""Set new preset mode."""
|
|
target_mode = VentilationMode.to_vicare_mode(preset_mode)
|
|
_LOGGER.debug("changing ventilation mode to %s", target_mode)
|
|
self._api.setActiveMode(target_mode)
|