mirror of
https://github.com/home-assistant/core.git
synced 2025-04-25 01:38:02 +00:00
Use DataUpdateCoordinator in Vallox (#56966)
This commit is contained in:
parent
02372cd65a
commit
6c01ed8d97
@ -1,7 +1,7 @@
|
||||
"""Support for Vallox ventilation units."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from dataclasses import dataclass, field
|
||||
import ipaddress
|
||||
import logging
|
||||
from typing import Any
|
||||
@ -11,13 +11,12 @@ from vallox_websocket_api.constants import vlxDevConstants
|
||||
from vallox_websocket_api.exceptions import ValloxApiException
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import CONF_HOST, CONF_NAME
|
||||
from homeassistant.const import CONF_HOST, CONF_NAME, EVENT_HOMEASSISTANT_STARTED
|
||||
from homeassistant.core import HomeAssistant, ServiceCall
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.discovery import async_load_platform
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.helpers.event import async_track_time_interval
|
||||
from homeassistant.helpers.typing import ConfigType, StateType
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import (
|
||||
DEFAULT_FAN_SPEED_AWAY,
|
||||
@ -28,8 +27,7 @@ from .const import (
|
||||
METRIC_KEY_PROFILE_FAN_SPEED_AWAY,
|
||||
METRIC_KEY_PROFILE_FAN_SPEED_BOOST,
|
||||
METRIC_KEY_PROFILE_FAN_SPEED_HOME,
|
||||
SIGNAL_VALLOX_STATE_UPDATE,
|
||||
STATE_PROXY_SCAN_INTERVAL,
|
||||
STATE_SCAN_INTERVAL,
|
||||
STR_TO_VALLOX_PROFILE_SETTABLE,
|
||||
)
|
||||
|
||||
@ -91,6 +89,40 @@ SERVICE_TO_METHOD = {
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class ValloxState:
|
||||
"""Describes the current state of the unit."""
|
||||
|
||||
metric_cache: dict[str, Any] = field(default_factory=dict)
|
||||
profile: VALLOX_PROFILE = VALLOX_PROFILE.NONE
|
||||
|
||||
def get_metric(self, metric_key: str) -> StateType:
|
||||
"""Return cached state value."""
|
||||
_LOGGER.debug("Fetching metric key: %s", metric_key)
|
||||
|
||||
if metric_key not in vlxDevConstants.__dict__:
|
||||
_LOGGER.debug("Metric key invalid: %s", metric_key)
|
||||
|
||||
if (value := self.metric_cache.get(metric_key)) is None:
|
||||
return None
|
||||
|
||||
if not isinstance(value, (str, int, float)):
|
||||
_LOGGER.debug(
|
||||
"Return value of metric %s has unexpected type %s",
|
||||
metric_key,
|
||||
type(value),
|
||||
)
|
||||
return None
|
||||
|
||||
return value
|
||||
|
||||
|
||||
class ValloxDataUpdateCoordinator(DataUpdateCoordinator):
|
||||
"""The DataUpdateCoordinator for Vallox."""
|
||||
|
||||
data: ValloxState
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up the client and boot the platforms."""
|
||||
conf = config[DOMAIN]
|
||||
@ -98,102 +130,74 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
name = conf.get(CONF_NAME)
|
||||
|
||||
client = Vallox(host)
|
||||
state_proxy = ValloxStateProxy(hass, client)
|
||||
service_handler = ValloxServiceHandler(client, state_proxy)
|
||||
|
||||
hass.data[DOMAIN] = {"client": client, "state_proxy": state_proxy, "name": name}
|
||||
async def async_update_data() -> ValloxState:
|
||||
"""Fetch state update."""
|
||||
_LOGGER.debug("Updating Vallox state cache")
|
||||
|
||||
try:
|
||||
metric_cache = await client.fetch_metrics()
|
||||
profile = await client.get_profile()
|
||||
|
||||
except (OSError, ValloxApiException) as err:
|
||||
raise UpdateFailed("Error during state cache update") from err
|
||||
|
||||
return ValloxState(metric_cache, profile)
|
||||
|
||||
coordinator = ValloxDataUpdateCoordinator(
|
||||
hass,
|
||||
_LOGGER,
|
||||
name=f"{name} DataUpdateCoordinator",
|
||||
update_interval=STATE_SCAN_INTERVAL,
|
||||
update_method=async_update_data,
|
||||
)
|
||||
|
||||
service_handler = ValloxServiceHandler(client, coordinator)
|
||||
for vallox_service, method in SERVICE_TO_METHOD.items():
|
||||
schema = method["schema"]
|
||||
hass.services.async_register(
|
||||
DOMAIN, vallox_service, service_handler.async_handle, schema=schema
|
||||
)
|
||||
|
||||
# The vallox hardware expects quite strict timings for websocket requests. Timings that machines
|
||||
# with less processing power, like Raspberries, cannot live up to during the busy start phase of
|
||||
# Home Asssistant. Hence, async_add_entities() for fan and sensor in respective code will be
|
||||
# called with update_before_add=False to intentionally delay the first request, increasing
|
||||
# chance that it is issued only when the machine is less busy again.
|
||||
hass.async_create_task(async_load_platform(hass, "sensor", DOMAIN, {}, config))
|
||||
hass.async_create_task(async_load_platform(hass, "fan", DOMAIN, {}, config))
|
||||
hass.data[DOMAIN] = {"client": client, "coordinator": coordinator, "name": name}
|
||||
|
||||
async_track_time_interval(hass, state_proxy.async_update, STATE_PROXY_SCAN_INTERVAL)
|
||||
async def _async_load_platform_delayed(*_: Any) -> None:
|
||||
await coordinator.async_refresh()
|
||||
hass.async_create_task(async_load_platform(hass, "sensor", DOMAIN, {}, config))
|
||||
hass.async_create_task(async_load_platform(hass, "fan", DOMAIN, {}, config))
|
||||
|
||||
# The Vallox hardware expects quite strict timings for websocket requests. Timings that machines
|
||||
# with less processing power, like a Raspberry Pi, cannot live up to during the busy start phase
|
||||
# of Home Asssistant.
|
||||
#
|
||||
# Hence, wait for the started event before doing a first data refresh and loading the platforms,
|
||||
# because it usually means the system is less busy after the event and can now meet the
|
||||
# websocket timing requirements.
|
||||
hass.bus.async_listen_once(
|
||||
EVENT_HOMEASSISTANT_STARTED, _async_load_platform_delayed
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class ValloxStateProxy:
|
||||
"""Helper class to reduce websocket API calls."""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, client: Vallox) -> None:
|
||||
"""Initialize the proxy."""
|
||||
self._hass = hass
|
||||
self._client = client
|
||||
self._metric_cache: dict[str, Any] = {}
|
||||
self._profile = VALLOX_PROFILE.NONE
|
||||
self._valid = False
|
||||
|
||||
def fetch_metric(self, metric_key: str) -> StateType:
|
||||
"""Return cached state value."""
|
||||
_LOGGER.debug("Fetching metric key: %s", metric_key)
|
||||
|
||||
if not self._valid:
|
||||
raise OSError("Device state out of sync.")
|
||||
|
||||
if metric_key not in vlxDevConstants.__dict__:
|
||||
raise KeyError(f"Unknown metric key: {metric_key}")
|
||||
|
||||
if (value := self._metric_cache[metric_key]) is None:
|
||||
return None
|
||||
|
||||
if not isinstance(value, (str, int, float)):
|
||||
raise TypeError(
|
||||
f"Return value of metric {metric_key} has unexpected type {type(value)}"
|
||||
)
|
||||
|
||||
return value
|
||||
|
||||
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 self._profile
|
||||
|
||||
async def async_update(self, time: datetime | None = None) -> None:
|
||||
"""Fetch state update."""
|
||||
_LOGGER.debug("Updating Vallox state cache")
|
||||
|
||||
try:
|
||||
self._metric_cache = await self._client.fetch_metrics()
|
||||
self._profile = await self._client.get_profile()
|
||||
|
||||
except (OSError, ValloxApiException) as err:
|
||||
self._valid = False
|
||||
_LOGGER.error("Error during state cache update: %s", err)
|
||||
return
|
||||
|
||||
self._valid = True
|
||||
async_dispatcher_send(self._hass, SIGNAL_VALLOX_STATE_UPDATE)
|
||||
|
||||
|
||||
class ValloxServiceHandler:
|
||||
"""Services implementation."""
|
||||
|
||||
def __init__(self, client: Vallox, state_proxy: ValloxStateProxy) -> None:
|
||||
def __init__(
|
||||
self, client: Vallox, coordinator: DataUpdateCoordinator[ValloxState]
|
||||
) -> None:
|
||||
"""Initialize the proxy."""
|
||||
self._client = client
|
||||
self._state_proxy = state_proxy
|
||||
self._coordinator = coordinator
|
||||
|
||||
async def async_set_profile(self, profile: str = "Home") -> bool:
|
||||
"""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"
|
||||
"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:
|
||||
@ -269,4 +273,4 @@ class ValloxServiceHandler:
|
||||
# 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()
|
||||
await self._coordinator.async_request_refresh()
|
||||
|
@ -7,8 +7,7 @@ from vallox_websocket_api import PROFILE as VALLOX_PROFILE
|
||||
DOMAIN = "vallox"
|
||||
DEFAULT_NAME = "Vallox"
|
||||
|
||||
SIGNAL_VALLOX_STATE_UPDATE = "vallox_state_update"
|
||||
STATE_PROXY_SCAN_INTERVAL = timedelta(seconds=60)
|
||||
STATE_SCAN_INTERVAL = timedelta(seconds=60)
|
||||
|
||||
# Common metric keys and (default) values.
|
||||
METRIC_KEY_MODE = "A_CYC_MODE"
|
||||
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
import logging
|
||||
from typing import Any
|
||||
from typing import Any, NamedTuple
|
||||
|
||||
from vallox_websocket_api import Vallox
|
||||
from vallox_websocket_api.exceptions import ValloxApiException
|
||||
@ -13,12 +13,12 @@ from homeassistant.components.fan import (
|
||||
FanEntity,
|
||||
NotValidPresetModeError,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from . import ValloxStateProxy
|
||||
from . import ValloxDataUpdateCoordinator
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
METRIC_KEY_MODE,
|
||||
@ -27,25 +27,38 @@ from .const import (
|
||||
METRIC_KEY_PROFILE_FAN_SPEED_HOME,
|
||||
MODE_OFF,
|
||||
MODE_ON,
|
||||
SIGNAL_VALLOX_STATE_UPDATE,
|
||||
STR_TO_VALLOX_PROFILE_SETTABLE,
|
||||
VALLOX_PROFILE_TO_STR_SETTABLE,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ATTR_PROFILE_FAN_SPEED_HOME = {
|
||||
"description": "fan_speed_home",
|
||||
"metric_key": METRIC_KEY_PROFILE_FAN_SPEED_HOME,
|
||||
}
|
||||
ATTR_PROFILE_FAN_SPEED_AWAY = {
|
||||
"description": "fan_speed_away",
|
||||
"metric_key": METRIC_KEY_PROFILE_FAN_SPEED_AWAY,
|
||||
}
|
||||
ATTR_PROFILE_FAN_SPEED_BOOST = {
|
||||
"description": "fan_speed_boost",
|
||||
"metric_key": METRIC_KEY_PROFILE_FAN_SPEED_BOOST,
|
||||
}
|
||||
|
||||
class ExtraStateAttributeDetails(NamedTuple):
|
||||
"""Extra state attribute properties."""
|
||||
|
||||
description: str
|
||||
metric_key: str
|
||||
|
||||
|
||||
EXTRA_STATE_ATTRIBUTES = (
|
||||
ExtraStateAttributeDetails(
|
||||
description="fan_speed_home", metric_key=METRIC_KEY_PROFILE_FAN_SPEED_HOME
|
||||
),
|
||||
ExtraStateAttributeDetails(
|
||||
description="fan_speed_away", metric_key=METRIC_KEY_PROFILE_FAN_SPEED_AWAY
|
||||
),
|
||||
ExtraStateAttributeDetails(
|
||||
description="fan_speed_boost", metric_key=METRIC_KEY_PROFILE_FAN_SPEED_BOOST
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _convert_fan_speed_value(value: StateType) -> int | None:
|
||||
if isinstance(value, (int, float)):
|
||||
return int(value)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
@ -62,31 +75,29 @@ async def async_setup_platform(
|
||||
client.set_settable_address(METRIC_KEY_MODE, int)
|
||||
|
||||
device = ValloxFan(
|
||||
hass.data[DOMAIN]["name"], client, hass.data[DOMAIN]["state_proxy"]
|
||||
hass.data[DOMAIN]["name"], client, hass.data[DOMAIN]["coordinator"]
|
||||
)
|
||||
|
||||
async_add_entities([device], update_before_add=False)
|
||||
async_add_entities([device])
|
||||
|
||||
|
||||
class ValloxFan(FanEntity):
|
||||
class ValloxFan(CoordinatorEntity, FanEntity):
|
||||
"""Representation of the fan."""
|
||||
|
||||
_attr_should_poll = False
|
||||
coordinator: ValloxDataUpdateCoordinator
|
||||
|
||||
def __init__(
|
||||
self, name: str, client: Vallox, state_proxy: ValloxStateProxy
|
||||
self,
|
||||
name: str,
|
||||
client: Vallox,
|
||||
coordinator: ValloxDataUpdateCoordinator,
|
||||
) -> None:
|
||||
"""Initialize the fan."""
|
||||
super().__init__(coordinator)
|
||||
|
||||
self._client = client
|
||||
self._state_proxy = state_proxy
|
||||
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
|
||||
|
||||
self._attr_name = name
|
||||
self._attr_available = False
|
||||
|
||||
@property
|
||||
def supported_features(self) -> int:
|
||||
@ -102,73 +113,24 @@ class ValloxFan(FanEntity):
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return if device is on."""
|
||||
return self._is_on
|
||||
return self.coordinator.data.get_metric(METRIC_KEY_MODE) == MODE_ON
|
||||
|
||||
@property
|
||||
def preset_mode(self) -> str | None:
|
||||
"""Return the current preset mode."""
|
||||
return self._preset_mode
|
||||
vallox_profile = self.coordinator.data.profile
|
||||
return VALLOX_PROFILE_TO_STR_SETTABLE.get(vallox_profile)
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self) -> Mapping[str, int | None]:
|
||||
"""Return device specific state attributes."""
|
||||
data = self.coordinator.data
|
||||
|
||||
return {
|
||||
ATTR_PROFILE_FAN_SPEED_HOME["description"]: self._fan_speed_home,
|
||||
ATTR_PROFILE_FAN_SPEED_AWAY["description"]: self._fan_speed_away,
|
||||
ATTR_PROFILE_FAN_SPEED_BOOST["description"]: self._fan_speed_boost,
|
||||
attr.description: _convert_fan_speed_value(data.get_metric(attr.metric_key))
|
||||
for attr in EXTRA_STATE_ATTRIBUTES
|
||||
}
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Call to update."""
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass, SIGNAL_VALLOX_STATE_UPDATE, self._update_callback
|
||||
)
|
||||
)
|
||||
|
||||
@callback
|
||||
def _update_callback(self) -> None:
|
||||
"""Call update method."""
|
||||
self.async_schedule_update_ha_state(True)
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Fetch state from the device."""
|
||||
try:
|
||||
# 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"]
|
||||
)
|
||||
fan_speed_away = self._state_proxy.fetch_metric(
|
||||
ATTR_PROFILE_FAN_SPEED_AWAY["metric_key"]
|
||||
)
|
||||
fan_speed_boost = self._state_proxy.fetch_metric(
|
||||
ATTR_PROFILE_FAN_SPEED_BOOST["metric_key"]
|
||||
)
|
||||
|
||||
except (OSError, KeyError, TypeError) as err:
|
||||
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
|
||||
)
|
||||
self._fan_speed_away = (
|
||||
int(fan_speed_away) if isinstance(fan_speed_away, (int, float)) else None
|
||||
)
|
||||
self._fan_speed_boost = (
|
||||
int(fan_speed_boost) if isinstance(fan_speed_boost, (int, float)) else None
|
||||
)
|
||||
|
||||
self._attr_available = True
|
||||
|
||||
async def _async_set_preset_mode_internal(self, preset_mode: str) -> bool:
|
||||
"""
|
||||
Set new preset mode.
|
||||
@ -201,7 +163,7 @@ class ValloxFan(FanEntity):
|
||||
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()
|
||||
await self.coordinator.async_request_refresh()
|
||||
|
||||
async def async_turn_on(
|
||||
self,
|
||||
@ -211,7 +173,7 @@ class ValloxFan(FanEntity):
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Turn the device on."""
|
||||
_LOGGER.debug("Turn on: %s", speed)
|
||||
_LOGGER.debug("Turn on")
|
||||
|
||||
update_needed = False
|
||||
|
||||
@ -231,7 +193,7 @@ class ValloxFan(FanEntity):
|
||||
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()
|
||||
await self.coordinator.async_request_refresh()
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the device off."""
|
||||
@ -246,4 +208,4 @@ class ValloxFan(FanEntity):
|
||||
return
|
||||
|
||||
# Same as for turn_on method.
|
||||
await self._state_proxy.async_update()
|
||||
await self.coordinator.async_request_refresh()
|
||||
|
@ -19,143 +19,91 @@ from homeassistant.const import (
|
||||
PERCENTAGE,
|
||||
TEMP_CELSIUS,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from . import ValloxStateProxy
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
METRIC_KEY_MODE,
|
||||
MODE_ON,
|
||||
SIGNAL_VALLOX_STATE_UPDATE,
|
||||
VALLOX_PROFILE_TO_STR_REPORTABLE,
|
||||
)
|
||||
from . import ValloxDataUpdateCoordinator
|
||||
from .const import DOMAIN, METRIC_KEY_MODE, MODE_ON, VALLOX_PROFILE_TO_STR_REPORTABLE
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ValloxSensor(SensorEntity):
|
||||
class ValloxSensor(CoordinatorEntity, SensorEntity):
|
||||
"""Representation of a Vallox sensor."""
|
||||
|
||||
_attr_should_poll = False
|
||||
entity_description: ValloxSensorEntityDescription
|
||||
coordinator: ValloxDataUpdateCoordinator
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
state_proxy: ValloxStateProxy,
|
||||
coordinator: ValloxDataUpdateCoordinator,
|
||||
description: ValloxSensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the Vallox sensor."""
|
||||
self._state_proxy = state_proxy
|
||||
super().__init__(coordinator)
|
||||
|
||||
self.entity_description = description
|
||||
|
||||
self._attr_name = f"{name} {description.name}"
|
||||
self._attr_available = False
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Call to update."""
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass, SIGNAL_VALLOX_STATE_UPDATE, self._update_callback
|
||||
)
|
||||
)
|
||||
|
||||
@callback
|
||||
def _update_callback(self) -> None:
|
||||
"""Call update method."""
|
||||
self.async_schedule_update_ha_state(True)
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Fetch state from the ventilation unit."""
|
||||
@property
|
||||
def native_value(self) -> StateType:
|
||||
"""Return the value reported by the sensor."""
|
||||
if (metric_key := self.entity_description.metric_key) is None:
|
||||
self._attr_available = False
|
||||
_LOGGER.error("Error updating sensor. Empty metric key")
|
||||
return
|
||||
_LOGGER.debug("Error updating sensor. Empty metric key")
|
||||
return None
|
||||
|
||||
try:
|
||||
self._attr_native_value = self._state_proxy.fetch_metric(metric_key)
|
||||
|
||||
except (OSError, KeyError, TypeError) as err:
|
||||
self._attr_available = False
|
||||
_LOGGER.error("Error updating sensor: %s", err)
|
||||
return
|
||||
|
||||
self._attr_available = True
|
||||
return self.coordinator.data.get_metric(metric_key)
|
||||
|
||||
|
||||
class ValloxProfileSensor(ValloxSensor):
|
||||
"""Child class for profile reporting."""
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Fetch state from the ventilation unit."""
|
||||
try:
|
||||
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
|
||||
@property
|
||||
def native_value(self) -> StateType:
|
||||
"""Return the value reported by the sensor."""
|
||||
vallox_profile = self.coordinator.data.profile
|
||||
return VALLOX_PROFILE_TO_STR_REPORTABLE.get(vallox_profile)
|
||||
|
||||
|
||||
# There seems to be a quirk with respect to the fan speed reporting. The device keeps on reporting
|
||||
# the last valid fan speed from when the device was in regular operation mode, even if it left that
|
||||
# state and has been shut off in the meantime.
|
||||
# There is a quirk with respect to the fan speed reporting. The device keeps on reporting the last
|
||||
# valid fan speed from when the device was in regular operation mode, even if it left that state and
|
||||
# has been shut off in the meantime.
|
||||
#
|
||||
# Therefore, first query the overall state of the device, and report zero percent fan speed in case
|
||||
# it is not in regular operation mode.
|
||||
class ValloxFanSpeedSensor(ValloxSensor):
|
||||
"""Child class for fan speed reporting."""
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Fetch state from the ventilation unit."""
|
||||
try:
|
||||
fan_on = self._state_proxy.fetch_metric(METRIC_KEY_MODE) == MODE_ON
|
||||
|
||||
except (OSError, KeyError, TypeError) as err:
|
||||
self._attr_available = False
|
||||
_LOGGER.error("Error updating sensor: %s", err)
|
||||
return
|
||||
|
||||
if fan_on:
|
||||
await super().async_update()
|
||||
else:
|
||||
# Report zero percent otherwise.
|
||||
self._attr_native_value = 0
|
||||
self._attr_available = True
|
||||
@property
|
||||
def native_value(self) -> StateType:
|
||||
"""Return the value reported by the sensor."""
|
||||
fan_is_on = self.coordinator.data.get_metric(METRIC_KEY_MODE) == MODE_ON
|
||||
return super().native_value if fan_is_on else 0
|
||||
|
||||
|
||||
class ValloxFilterRemainingSensor(ValloxSensor):
|
||||
"""Child class for filter remaining time reporting."""
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Fetch state from the ventilation unit."""
|
||||
await super().async_update()
|
||||
@property
|
||||
def native_value(self) -> StateType:
|
||||
"""Return the value reported by the sensor."""
|
||||
super_native_value = super().native_value
|
||||
|
||||
# Check if the update in the super call was a success.
|
||||
if not self._attr_available:
|
||||
return
|
||||
|
||||
if not isinstance(self._attr_native_value, (int, float)):
|
||||
self._attr_available = False
|
||||
_LOGGER.error(
|
||||
"Value has unexpected type: %s", type(self._attr_native_value)
|
||||
)
|
||||
return
|
||||
if not isinstance(super_native_value, (int, float)):
|
||||
_LOGGER.debug("Value has unexpected type: %s", type(super_native_value))
|
||||
return None
|
||||
|
||||
# Since only a delta of days is received from the device, fix the time so the timestamp does
|
||||
# not change with every update.
|
||||
days_remaining = float(self._attr_native_value)
|
||||
days_remaining = float(super_native_value)
|
||||
days_remaining_delta = timedelta(days=days_remaining)
|
||||
now = datetime.utcnow().replace(hour=13, minute=0, second=0, microsecond=0)
|
||||
|
||||
self._attr_native_value = (now + days_remaining_delta).isoformat()
|
||||
return (now + days_remaining_delta).isoformat()
|
||||
|
||||
|
||||
@dataclass
|
||||
@ -259,12 +207,11 @@ async def async_setup_platform(
|
||||
return
|
||||
|
||||
name = hass.data[DOMAIN]["name"]
|
||||
state_proxy = hass.data[DOMAIN]["state_proxy"]
|
||||
coordinator = hass.data[DOMAIN]["coordinator"]
|
||||
|
||||
async_add_entities(
|
||||
[
|
||||
description.sensor_type(name, state_proxy, description)
|
||||
description.sensor_type(name, coordinator, description)
|
||||
for description in SENSORS
|
||||
],
|
||||
update_before_add=False,
|
||||
]
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user