mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
Migrate Vallox to new fan entity model (#56663)
* Migrate Vallox to new fan entity model * Review comments 1 * Minor corrections * Review comments 2
This commit is contained in:
parent
00651a4055
commit
d13c3e3917
@ -30,6 +30,7 @@ from .const import (
|
||||
METRIC_KEY_PROFILE_FAN_SPEED_HOME,
|
||||
SIGNAL_VALLOX_STATE_UPDATE,
|
||||
STATE_PROXY_SCAN_INTERVAL,
|
||||
STR_TO_VALLOX_PROFILE_SETTABLE,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@ -46,25 +47,15 @@ CONFIG_SCHEMA = vol.Schema(
|
||||
extra=vol.ALLOW_EXTRA,
|
||||
)
|
||||
|
||||
PROFILE_TO_STR_SETTABLE = {
|
||||
VALLOX_PROFILE.HOME: "Home",
|
||||
VALLOX_PROFILE.AWAY: "Away",
|
||||
VALLOX_PROFILE.BOOST: "Boost",
|
||||
VALLOX_PROFILE.FIREPLACE: "Fireplace",
|
||||
}
|
||||
|
||||
STR_TO_PROFILE = {v: k for (k, v) in PROFILE_TO_STR_SETTABLE.items()}
|
||||
|
||||
PROFILE_TO_STR_REPORTABLE = {
|
||||
**{VALLOX_PROFILE.NONE: "None", VALLOX_PROFILE.EXTRA: "Extra"},
|
||||
**PROFILE_TO_STR_SETTABLE,
|
||||
}
|
||||
|
||||
ATTR_PROFILE = "profile"
|
||||
ATTR_PROFILE_FAN_SPEED = "fan_speed"
|
||||
|
||||
SERVICE_SCHEMA_SET_PROFILE = vol.Schema(
|
||||
{vol.Required(ATTR_PROFILE): vol.All(cv.string, vol.In(STR_TO_PROFILE))}
|
||||
{
|
||||
vol.Required(ATTR_PROFILE): vol.All(
|
||||
cv.string, vol.In(STR_TO_VALLOX_PROFILE_SETTABLE)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
SERVICE_SCHEMA_SET_PROFILE_FAN_SPEED = vol.Schema(
|
||||
@ -163,14 +154,14 @@ class ValloxStateProxy:
|
||||
|
||||
return value
|
||||
|
||||
def get_profile(self) -> str:
|
||||
def get_profile(self) -> VALLOX_PROFILE:
|
||||
"""Return cached profile value."""
|
||||
_LOGGER.debug("Returning profile")
|
||||
|
||||
if not self._valid:
|
||||
raise OSError("Device state out of sync.")
|
||||
|
||||
return PROFILE_TO_STR_REPORTABLE[self._profile]
|
||||
return self._profile
|
||||
|
||||
async def async_update(self, time: datetime | None = None) -> None:
|
||||
"""Fetch state update."""
|
||||
@ -201,8 +192,13 @@ class ValloxServiceHandler:
|
||||
"""Set the ventilation profile."""
|
||||
_LOGGER.debug("Setting ventilation profile to: %s", profile)
|
||||
|
||||
_LOGGER.warning(
|
||||
"Attention: The service 'vallox.set_profile' is superseded by the 'fan.set_preset_mode' service."
|
||||
"It will be removed in the future, please migrate to 'fan.set_preset_mode' to prevent breakage"
|
||||
)
|
||||
|
||||
try:
|
||||
await self._client.set_profile(STR_TO_PROFILE[profile])
|
||||
await self._client.set_profile(STR_TO_VALLOX_PROFILE_SETTABLE[profile])
|
||||
return True
|
||||
|
||||
except (OSError, ValloxApiException) as err:
|
||||
@ -271,6 +267,7 @@ class ValloxServiceHandler:
|
||||
|
||||
result = await getattr(self, method["method"])(**params)
|
||||
|
||||
# Force state_proxy to refresh device state, so that updates are propagated to platforms.
|
||||
# This state change affects other entities like sensors. Force an immediate update that can
|
||||
# be observed by all parties involved.
|
||||
if result:
|
||||
await self._state_proxy.async_update()
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from vallox_websocket_api import PROFILE as VALLOX_PROFILE
|
||||
|
||||
DOMAIN = "vallox"
|
||||
DEFAULT_NAME = "Vallox"
|
||||
|
||||
@ -20,3 +22,19 @@ MODE_OFF = 5
|
||||
DEFAULT_FAN_SPEED_HOME = 50
|
||||
DEFAULT_FAN_SPEED_AWAY = 25
|
||||
DEFAULT_FAN_SPEED_BOOST = 65
|
||||
|
||||
VALLOX_PROFILE_TO_STR_SETTABLE = {
|
||||
VALLOX_PROFILE.HOME: "Home",
|
||||
VALLOX_PROFILE.AWAY: "Away",
|
||||
VALLOX_PROFILE.BOOST: "Boost",
|
||||
VALLOX_PROFILE.FIREPLACE: "Fireplace",
|
||||
}
|
||||
|
||||
VALLOX_PROFILE_TO_STR_REPORTABLE = {
|
||||
VALLOX_PROFILE.EXTRA: "Extra",
|
||||
**VALLOX_PROFILE_TO_STR_SETTABLE,
|
||||
}
|
||||
|
||||
STR_TO_VALLOX_PROFILE_SETTABLE = {
|
||||
value: key for (key, value) in VALLOX_PROFILE_TO_STR_SETTABLE.items()
|
||||
}
|
||||
|
@ -6,8 +6,13 @@ import logging
|
||||
from typing import Any
|
||||
|
||||
from vallox_websocket_api import Vallox
|
||||
from vallox_websocket_api.exceptions import ValloxApiException
|
||||
|
||||
from homeassistant.components.fan import FanEntity
|
||||
from homeassistant.components.fan import (
|
||||
SUPPORT_PRESET_MODE,
|
||||
FanEntity,
|
||||
NotValidPresetModeError,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
@ -23,11 +28,12 @@ from .const import (
|
||||
MODE_OFF,
|
||||
MODE_ON,
|
||||
SIGNAL_VALLOX_STATE_UPDATE,
|
||||
STR_TO_VALLOX_PROFILE_SETTABLE,
|
||||
VALLOX_PROFILE_TO_STR_SETTABLE,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Device attributes
|
||||
ATTR_PROFILE_FAN_SPEED_HOME = {
|
||||
"description": "fan_speed_home",
|
||||
"metric_key": METRIC_KEY_PROFILE_FAN_SPEED_HOME,
|
||||
@ -65,39 +71,44 @@ async def async_setup_platform(
|
||||
class ValloxFan(FanEntity):
|
||||
"""Representation of the fan."""
|
||||
|
||||
_attr_should_poll = False
|
||||
|
||||
def __init__(
|
||||
self, name: str, client: Vallox, state_proxy: ValloxStateProxy
|
||||
) -> None:
|
||||
"""Initialize the fan."""
|
||||
self._name = name
|
||||
self._client = client
|
||||
self._state_proxy = state_proxy
|
||||
self._available = False
|
||||
self._is_on = False
|
||||
self._preset_mode: str | None = None
|
||||
self._fan_speed_home: int | None = None
|
||||
self._fan_speed_away: int | None = None
|
||||
self._fan_speed_boost: int | None = None
|
||||
|
||||
@property
|
||||
def should_poll(self) -> bool:
|
||||
"""Do not poll the device."""
|
||||
return False
|
||||
self._attr_name = name
|
||||
self._attr_available = False
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Return the name of the device."""
|
||||
return self._name
|
||||
def supported_features(self) -> int:
|
||||
"""Flag supported features."""
|
||||
return SUPPORT_PRESET_MODE
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return if state is known."""
|
||||
return self._available
|
||||
def preset_modes(self) -> list[str]:
|
||||
"""Return a list of available preset modes."""
|
||||
# Use the Vallox profile names for the preset names.
|
||||
return list(STR_TO_VALLOX_PROFILE_SETTABLE.keys())
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return if device is on."""
|
||||
return self._is_on
|
||||
|
||||
@property
|
||||
def preset_mode(self) -> str | None:
|
||||
"""Return the current preset mode."""
|
||||
return self._preset_mode
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self) -> Mapping[str, int | None]:
|
||||
"""Return device specific state attributes."""
|
||||
@ -126,6 +137,8 @@ class ValloxFan(FanEntity):
|
||||
# Fetch if the whole device is in regular operation state.
|
||||
self._is_on = self._state_proxy.fetch_metric(METRIC_KEY_MODE) == MODE_ON
|
||||
|
||||
vallox_profile = self._state_proxy.get_profile()
|
||||
|
||||
# Fetch the profile fan speeds.
|
||||
fan_speed_home = self._state_proxy.fetch_metric(
|
||||
ATTR_PROFILE_FAN_SPEED_HOME["metric_key"]
|
||||
@ -138,10 +151,12 @@ class ValloxFan(FanEntity):
|
||||
)
|
||||
|
||||
except (OSError, KeyError, TypeError) as err:
|
||||
self._available = False
|
||||
self._attr_available = False
|
||||
_LOGGER.error("Error updating fan: %s", err)
|
||||
return
|
||||
|
||||
self._preset_mode = VALLOX_PROFILE_TO_STR_SETTABLE.get(vallox_profile)
|
||||
|
||||
self._fan_speed_home = (
|
||||
int(fan_speed_home) if isinstance(fan_speed_home, (int, float)) else None
|
||||
)
|
||||
@ -152,15 +167,42 @@ class ValloxFan(FanEntity):
|
||||
int(fan_speed_boost) if isinstance(fan_speed_boost, (int, float)) else None
|
||||
)
|
||||
|
||||
self._available = True
|
||||
self._attr_available = True
|
||||
|
||||
async def _async_set_preset_mode_internal(self, preset_mode: str) -> bool:
|
||||
"""
|
||||
Set new preset mode.
|
||||
|
||||
Returns true if the mode has been changed, false otherwise.
|
||||
"""
|
||||
try:
|
||||
self._valid_preset_mode_or_raise(preset_mode) # type: ignore[no-untyped-call]
|
||||
|
||||
except NotValidPresetModeError as err:
|
||||
_LOGGER.error(err)
|
||||
return False
|
||||
|
||||
if preset_mode == self.preset_mode:
|
||||
return False
|
||||
|
||||
try:
|
||||
await self._client.set_profile(STR_TO_VALLOX_PROFILE_SETTABLE[preset_mode])
|
||||
|
||||
except (OSError, ValloxApiException) as err:
|
||||
_LOGGER.error("Error setting preset: %s", err)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||
"""Set new preset mode."""
|
||||
update_needed = await self._async_set_preset_mode_internal(preset_mode)
|
||||
|
||||
if update_needed:
|
||||
# This state change affects other entities like sensors. Force an immediate update that
|
||||
# can be observed by all parties involved.
|
||||
await self._state_proxy.async_update()
|
||||
|
||||
#
|
||||
# The fan entity model has changed to use percentages and preset_modes
|
||||
# instead of speeds.
|
||||
#
|
||||
# Please review
|
||||
# https://developers.home-assistant.io/docs/core/entity/fan/
|
||||
#
|
||||
async def async_turn_on(
|
||||
self,
|
||||
speed: str | None = None,
|
||||
@ -171,39 +213,37 @@ class ValloxFan(FanEntity):
|
||||
"""Turn the device on."""
|
||||
_LOGGER.debug("Turn on: %s", speed)
|
||||
|
||||
# Only the case speed == None equals the GUI toggle switch being activated.
|
||||
if speed is not None:
|
||||
return
|
||||
update_needed = False
|
||||
|
||||
if self._is_on:
|
||||
_LOGGER.error("Already on")
|
||||
return
|
||||
if preset_mode:
|
||||
update_needed = await self._async_set_preset_mode_internal(preset_mode)
|
||||
|
||||
try:
|
||||
await self._client.set_values({METRIC_KEY_MODE: MODE_ON})
|
||||
if not self.is_on:
|
||||
try:
|
||||
await self._client.set_values({METRIC_KEY_MODE: MODE_ON})
|
||||
|
||||
except OSError as err:
|
||||
self._available = False
|
||||
_LOGGER.error("Error turning on: %s", err)
|
||||
return
|
||||
except OSError as err:
|
||||
_LOGGER.error("Error turning on: %s", err)
|
||||
|
||||
# This state change affects other entities like sensors. Force an immediate update that can
|
||||
# be observed by all parties involved.
|
||||
await self._state_proxy.async_update()
|
||||
else:
|
||||
update_needed = True
|
||||
|
||||
if update_needed:
|
||||
# This state change affects other entities like sensors. Force an immediate update that
|
||||
# can be observed by all parties involved.
|
||||
await self._state_proxy.async_update()
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the device off."""
|
||||
if not self._is_on:
|
||||
_LOGGER.error("Already off")
|
||||
if not self.is_on:
|
||||
return
|
||||
|
||||
try:
|
||||
await self._client.set_values({METRIC_KEY_MODE: MODE_OFF})
|
||||
|
||||
except OSError as err:
|
||||
self._available = False
|
||||
_LOGGER.error("Error turning off: %s", err)
|
||||
return
|
||||
|
||||
# Same as for turn_on method.
|
||||
await self._state_proxy.async_update(None)
|
||||
await self._state_proxy.async_update()
|
||||
|
@ -25,7 +25,13 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from . import ValloxStateProxy
|
||||
from .const import DOMAIN, METRIC_KEY_MODE, MODE_ON, SIGNAL_VALLOX_STATE_UPDATE
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
METRIC_KEY_MODE,
|
||||
MODE_ON,
|
||||
SIGNAL_VALLOX_STATE_UPDATE,
|
||||
VALLOX_PROFILE_TO_STR_REPORTABLE,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -89,13 +95,14 @@ class ValloxProfileSensor(ValloxSensor):
|
||||
async def async_update(self) -> None:
|
||||
"""Fetch state from the ventilation unit."""
|
||||
try:
|
||||
self._attr_native_value = self._state_proxy.get_profile()
|
||||
vallox_profile = self._state_proxy.get_profile()
|
||||
|
||||
except OSError as err:
|
||||
self._attr_available = False
|
||||
_LOGGER.error("Error updating sensor: %s", err)
|
||||
return
|
||||
|
||||
self._attr_native_value = VALLOX_PROFILE_TO_STR_REPORTABLE.get(vallox_profile)
|
||||
self._attr_available = True
|
||||
|
||||
|
||||
|
@ -15,7 +15,7 @@ set_profile:
|
||||
- 'Home'
|
||||
|
||||
set_profile_fan_speed_home:
|
||||
name: Set profile fan speed hom
|
||||
name: Set profile fan speed home
|
||||
description: Set the fan speed of the Home profile.
|
||||
fields:
|
||||
fan_speed:
|
||||
|
Loading…
x
Reference in New Issue
Block a user