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,
|
METRIC_KEY_PROFILE_FAN_SPEED_HOME,
|
||||||
SIGNAL_VALLOX_STATE_UPDATE,
|
SIGNAL_VALLOX_STATE_UPDATE,
|
||||||
STATE_PROXY_SCAN_INTERVAL,
|
STATE_PROXY_SCAN_INTERVAL,
|
||||||
|
STR_TO_VALLOX_PROFILE_SETTABLE,
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -46,25 +47,15 @@ CONFIG_SCHEMA = vol.Schema(
|
|||||||
extra=vol.ALLOW_EXTRA,
|
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 = "profile"
|
||||||
ATTR_PROFILE_FAN_SPEED = "fan_speed"
|
ATTR_PROFILE_FAN_SPEED = "fan_speed"
|
||||||
|
|
||||||
SERVICE_SCHEMA_SET_PROFILE = vol.Schema(
|
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(
|
SERVICE_SCHEMA_SET_PROFILE_FAN_SPEED = vol.Schema(
|
||||||
@ -163,14 +154,14 @@ class ValloxStateProxy:
|
|||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def get_profile(self) -> str:
|
def get_profile(self) -> VALLOX_PROFILE:
|
||||||
"""Return cached profile value."""
|
"""Return cached profile value."""
|
||||||
_LOGGER.debug("Returning profile")
|
_LOGGER.debug("Returning profile")
|
||||||
|
|
||||||
if not self._valid:
|
if not self._valid:
|
||||||
raise OSError("Device state out of sync.")
|
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:
|
async def async_update(self, time: datetime | None = None) -> None:
|
||||||
"""Fetch state update."""
|
"""Fetch state update."""
|
||||||
@ -201,8 +192,13 @@ class ValloxServiceHandler:
|
|||||||
"""Set the ventilation profile."""
|
"""Set the ventilation profile."""
|
||||||
_LOGGER.debug("Setting ventilation profile to: %s", 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:
|
try:
|
||||||
await self._client.set_profile(STR_TO_PROFILE[profile])
|
await self._client.set_profile(STR_TO_VALLOX_PROFILE_SETTABLE[profile])
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except (OSError, ValloxApiException) as err:
|
except (OSError, ValloxApiException) as err:
|
||||||
@ -271,6 +267,7 @@ class ValloxServiceHandler:
|
|||||||
|
|
||||||
result = await getattr(self, method["method"])(**params)
|
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:
|
if result:
|
||||||
await self._state_proxy.async_update()
|
await self._state_proxy.async_update()
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from vallox_websocket_api import PROFILE as VALLOX_PROFILE
|
||||||
|
|
||||||
DOMAIN = "vallox"
|
DOMAIN = "vallox"
|
||||||
DEFAULT_NAME = "Vallox"
|
DEFAULT_NAME = "Vallox"
|
||||||
|
|
||||||
@ -20,3 +22,19 @@ MODE_OFF = 5
|
|||||||
DEFAULT_FAN_SPEED_HOME = 50
|
DEFAULT_FAN_SPEED_HOME = 50
|
||||||
DEFAULT_FAN_SPEED_AWAY = 25
|
DEFAULT_FAN_SPEED_AWAY = 25
|
||||||
DEFAULT_FAN_SPEED_BOOST = 65
|
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 typing import Any
|
||||||
|
|
||||||
from vallox_websocket_api import Vallox
|
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.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
@ -23,11 +28,12 @@ from .const import (
|
|||||||
MODE_OFF,
|
MODE_OFF,
|
||||||
MODE_ON,
|
MODE_ON,
|
||||||
SIGNAL_VALLOX_STATE_UPDATE,
|
SIGNAL_VALLOX_STATE_UPDATE,
|
||||||
|
STR_TO_VALLOX_PROFILE_SETTABLE,
|
||||||
|
VALLOX_PROFILE_TO_STR_SETTABLE,
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Device attributes
|
|
||||||
ATTR_PROFILE_FAN_SPEED_HOME = {
|
ATTR_PROFILE_FAN_SPEED_HOME = {
|
||||||
"description": "fan_speed_home",
|
"description": "fan_speed_home",
|
||||||
"metric_key": METRIC_KEY_PROFILE_FAN_SPEED_HOME,
|
"metric_key": METRIC_KEY_PROFILE_FAN_SPEED_HOME,
|
||||||
@ -65,39 +71,44 @@ async def async_setup_platform(
|
|||||||
class ValloxFan(FanEntity):
|
class ValloxFan(FanEntity):
|
||||||
"""Representation of the fan."""
|
"""Representation of the fan."""
|
||||||
|
|
||||||
|
_attr_should_poll = False
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, name: str, client: Vallox, state_proxy: ValloxStateProxy
|
self, name: str, client: Vallox, state_proxy: ValloxStateProxy
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the fan."""
|
"""Initialize the fan."""
|
||||||
self._name = name
|
|
||||||
self._client = client
|
self._client = client
|
||||||
self._state_proxy = state_proxy
|
self._state_proxy = state_proxy
|
||||||
self._available = False
|
|
||||||
self._is_on = False
|
self._is_on = False
|
||||||
|
self._preset_mode: str | None = None
|
||||||
self._fan_speed_home: int | None = None
|
self._fan_speed_home: int | None = None
|
||||||
self._fan_speed_away: int | None = None
|
self._fan_speed_away: int | None = None
|
||||||
self._fan_speed_boost: int | None = None
|
self._fan_speed_boost: int | None = None
|
||||||
|
|
||||||
@property
|
self._attr_name = name
|
||||||
def should_poll(self) -> bool:
|
self._attr_available = False
|
||||||
"""Do not poll the device."""
|
|
||||||
return False
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def supported_features(self) -> int:
|
||||||
"""Return the name of the device."""
|
"""Flag supported features."""
|
||||||
return self._name
|
return SUPPORT_PRESET_MODE
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available(self) -> bool:
|
def preset_modes(self) -> list[str]:
|
||||||
"""Return if state is known."""
|
"""Return a list of available preset modes."""
|
||||||
return self._available
|
# Use the Vallox profile names for the preset names.
|
||||||
|
return list(STR_TO_VALLOX_PROFILE_SETTABLE.keys())
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> bool:
|
def is_on(self) -> bool:
|
||||||
"""Return if device is on."""
|
"""Return if device is on."""
|
||||||
return self._is_on
|
return self._is_on
|
||||||
|
|
||||||
|
@property
|
||||||
|
def preset_mode(self) -> str | None:
|
||||||
|
"""Return the current preset mode."""
|
||||||
|
return self._preset_mode
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def extra_state_attributes(self) -> Mapping[str, int | None]:
|
def extra_state_attributes(self) -> Mapping[str, int | None]:
|
||||||
"""Return device specific state attributes."""
|
"""Return device specific state attributes."""
|
||||||
@ -126,6 +137,8 @@ class ValloxFan(FanEntity):
|
|||||||
# Fetch if the whole device is in regular operation state.
|
# Fetch if the whole device is in regular operation state.
|
||||||
self._is_on = self._state_proxy.fetch_metric(METRIC_KEY_MODE) == MODE_ON
|
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.
|
# Fetch the profile fan speeds.
|
||||||
fan_speed_home = self._state_proxy.fetch_metric(
|
fan_speed_home = self._state_proxy.fetch_metric(
|
||||||
ATTR_PROFILE_FAN_SPEED_HOME["metric_key"]
|
ATTR_PROFILE_FAN_SPEED_HOME["metric_key"]
|
||||||
@ -138,10 +151,12 @@ class ValloxFan(FanEntity):
|
|||||||
)
|
)
|
||||||
|
|
||||||
except (OSError, KeyError, TypeError) as err:
|
except (OSError, KeyError, TypeError) as err:
|
||||||
self._available = False
|
self._attr_available = False
|
||||||
_LOGGER.error("Error updating fan: %s", err)
|
_LOGGER.error("Error updating fan: %s", err)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
self._preset_mode = VALLOX_PROFILE_TO_STR_SETTABLE.get(vallox_profile)
|
||||||
|
|
||||||
self._fan_speed_home = (
|
self._fan_speed_home = (
|
||||||
int(fan_speed_home) if isinstance(fan_speed_home, (int, float)) else None
|
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
|
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(
|
async def async_turn_on(
|
||||||
self,
|
self,
|
||||||
speed: str | None = None,
|
speed: str | None = None,
|
||||||
@ -171,39 +213,37 @@ class ValloxFan(FanEntity):
|
|||||||
"""Turn the device on."""
|
"""Turn the device on."""
|
||||||
_LOGGER.debug("Turn on: %s", speed)
|
_LOGGER.debug("Turn on: %s", speed)
|
||||||
|
|
||||||
# Only the case speed == None equals the GUI toggle switch being activated.
|
update_needed = False
|
||||||
if speed is not None:
|
|
||||||
return
|
|
||||||
|
|
||||||
if self._is_on:
|
if preset_mode:
|
||||||
_LOGGER.error("Already on")
|
update_needed = await self._async_set_preset_mode_internal(preset_mode)
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
if not self.is_on:
|
||||||
await self._client.set_values({METRIC_KEY_MODE: MODE_ON})
|
try:
|
||||||
|
await self._client.set_values({METRIC_KEY_MODE: MODE_ON})
|
||||||
|
|
||||||
except OSError as err:
|
except OSError as err:
|
||||||
self._available = False
|
_LOGGER.error("Error turning on: %s", err)
|
||||||
_LOGGER.error("Error turning on: %s", err)
|
|
||||||
return
|
|
||||||
|
|
||||||
# This state change affects other entities like sensors. Force an immediate update that can
|
else:
|
||||||
# be observed by all parties involved.
|
update_needed = True
|
||||||
await self._state_proxy.async_update()
|
|
||||||
|
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:
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
"""Turn the device off."""
|
"""Turn the device off."""
|
||||||
if not self._is_on:
|
if not self.is_on:
|
||||||
_LOGGER.error("Already off")
|
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self._client.set_values({METRIC_KEY_MODE: MODE_OFF})
|
await self._client.set_values({METRIC_KEY_MODE: MODE_OFF})
|
||||||
|
|
||||||
except OSError as err:
|
except OSError as err:
|
||||||
self._available = False
|
|
||||||
_LOGGER.error("Error turning off: %s", err)
|
_LOGGER.error("Error turning off: %s", err)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Same as for turn_on method.
|
# 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 homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||||
|
|
||||||
from . import ValloxStateProxy
|
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__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -89,13 +95,14 @@ class ValloxProfileSensor(ValloxSensor):
|
|||||||
async def async_update(self) -> None:
|
async def async_update(self) -> None:
|
||||||
"""Fetch state from the ventilation unit."""
|
"""Fetch state from the ventilation unit."""
|
||||||
try:
|
try:
|
||||||
self._attr_native_value = self._state_proxy.get_profile()
|
vallox_profile = self._state_proxy.get_profile()
|
||||||
|
|
||||||
except OSError as err:
|
except OSError as err:
|
||||||
self._attr_available = False
|
self._attr_available = False
|
||||||
_LOGGER.error("Error updating sensor: %s", err)
|
_LOGGER.error("Error updating sensor: %s", err)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
self._attr_native_value = VALLOX_PROFILE_TO_STR_REPORTABLE.get(vallox_profile)
|
||||||
self._attr_available = True
|
self._attr_available = True
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ set_profile:
|
|||||||
- 'Home'
|
- 'Home'
|
||||||
|
|
||||||
set_profile_fan_speed_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.
|
description: Set the fan speed of the Home profile.
|
||||||
fields:
|
fields:
|
||||||
fan_speed:
|
fan_speed:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user