2019-06-25 09:38:24 +00:00
|
|
|
"""Support for Vallox ventilation unit sensors."""
|
2021-08-24 08:14:34 +00:00
|
|
|
from __future__ import annotations
|
2019-06-25 09:38:24 +00:00
|
|
|
|
2021-08-24 08:14:34 +00:00
|
|
|
from dataclasses import dataclass
|
2019-06-25 09:38:24 +00:00
|
|
|
from datetime import datetime, timedelta
|
|
|
|
import logging
|
|
|
|
|
2021-08-24 08:14:34 +00:00
|
|
|
from homeassistant.components.sensor import (
|
|
|
|
STATE_CLASS_MEASUREMENT,
|
|
|
|
SensorEntity,
|
|
|
|
SensorEntityDescription,
|
|
|
|
)
|
2019-06-25 09:38:24 +00:00
|
|
|
from homeassistant.const import (
|
2021-07-19 08:56:26 +00:00
|
|
|
CONCENTRATION_PARTS_PER_MILLION,
|
|
|
|
DEVICE_CLASS_CO2,
|
2019-07-31 19:25:30 +00:00
|
|
|
DEVICE_CLASS_HUMIDITY,
|
|
|
|
DEVICE_CLASS_TEMPERATURE,
|
|
|
|
DEVICE_CLASS_TIMESTAMP,
|
2020-09-05 19:09:14 +00:00
|
|
|
PERCENTAGE,
|
2019-07-31 19:25:30 +00:00
|
|
|
TEMP_CELSIUS,
|
|
|
|
)
|
2021-09-23 17:59:28 +00:00
|
|
|
from homeassistant.core import HomeAssistant, callback
|
2019-06-25 09:38:24 +00:00
|
|
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
2021-09-23 17:59:28 +00:00
|
|
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|
|
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
2019-06-25 09:38:24 +00:00
|
|
|
|
2021-08-30 15:01:45 +00:00
|
|
|
from . import ValloxStateProxy
|
2021-09-29 15:14:41 +00:00
|
|
|
from .const import (
|
|
|
|
DOMAIN,
|
|
|
|
METRIC_KEY_MODE,
|
|
|
|
MODE_ON,
|
|
|
|
SIGNAL_VALLOX_STATE_UPDATE,
|
|
|
|
VALLOX_PROFILE_TO_STR_REPORTABLE,
|
|
|
|
)
|
2019-06-25 09:38:24 +00:00
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
2021-03-22 18:47:44 +00:00
|
|
|
class ValloxSensor(SensorEntity):
|
2019-06-25 09:38:24 +00:00
|
|
|
"""Representation of a Vallox sensor."""
|
|
|
|
|
2021-08-24 08:14:34 +00:00
|
|
|
_attr_should_poll = False
|
|
|
|
entity_description: ValloxSensorEntityDescription
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
def __init__(
|
2021-08-17 08:05:28 +00:00
|
|
|
self,
|
2021-08-24 08:14:34 +00:00
|
|
|
name: str,
|
|
|
|
state_proxy: ValloxStateProxy,
|
|
|
|
description: ValloxSensorEntityDescription,
|
2019-07-31 19:25:30 +00:00
|
|
|
) -> None:
|
2019-06-25 09:38:24 +00:00
|
|
|
"""Initialize the Vallox sensor."""
|
|
|
|
self._state_proxy = state_proxy
|
2021-08-24 08:14:34 +00:00
|
|
|
|
|
|
|
self.entity_description = description
|
|
|
|
|
|
|
|
self._attr_name = f"{name} {description.name}"
|
|
|
|
self._attr_available = False
|
2019-06-25 09:38:24 +00:00
|
|
|
|
2021-09-23 17:59:28 +00:00
|
|
|
async def async_added_to_hass(self) -> None:
|
2019-06-25 09:38:24 +00:00
|
|
|
"""Call to update."""
|
2020-04-02 16:25:33 +00:00
|
|
|
self.async_on_remove(
|
|
|
|
async_dispatcher_connect(
|
|
|
|
self.hass, SIGNAL_VALLOX_STATE_UPDATE, self._update_callback
|
|
|
|
)
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2019-06-25 09:38:24 +00:00
|
|
|
|
|
|
|
@callback
|
2021-09-23 17:59:28 +00:00
|
|
|
def _update_callback(self) -> None:
|
2019-06-25 09:38:24 +00:00
|
|
|
"""Call update method."""
|
|
|
|
self.async_schedule_update_ha_state(True)
|
|
|
|
|
2021-09-23 17:59:28 +00:00
|
|
|
async def async_update(self) -> None:
|
2019-06-25 09:38:24 +00:00
|
|
|
"""Fetch state from the ventilation unit."""
|
2021-10-17 18:02:42 +00:00
|
|
|
if (metric_key := self.entity_description.metric_key) is None:
|
2021-09-23 17:59:28 +00:00
|
|
|
self._attr_available = False
|
|
|
|
_LOGGER.error("Error updating sensor. Empty metric key")
|
|
|
|
return
|
|
|
|
|
2019-06-25 09:38:24 +00:00
|
|
|
try:
|
2021-09-23 17:59:28 +00:00
|
|
|
self._attr_native_value = self._state_proxy.fetch_metric(metric_key)
|
2019-06-25 09:38:24 +00:00
|
|
|
|
2021-09-23 17:59:28 +00:00
|
|
|
except (OSError, KeyError, TypeError) as err:
|
2021-08-24 08:14:34 +00:00
|
|
|
self._attr_available = False
|
|
|
|
_LOGGER.error("Error updating sensor: %s", err)
|
2021-09-06 10:03:45 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
self._attr_available = True
|
2021-08-24 08:14:34 +00:00
|
|
|
|
|
|
|
|
|
|
|
class ValloxProfileSensor(ValloxSensor):
|
|
|
|
"""Child class for profile reporting."""
|
|
|
|
|
2021-09-23 17:59:28 +00:00
|
|
|
async def async_update(self) -> None:
|
2021-08-24 08:14:34 +00:00
|
|
|
"""Fetch state from the ventilation unit."""
|
|
|
|
try:
|
2021-09-29 15:14:41 +00:00
|
|
|
vallox_profile = self._state_proxy.get_profile()
|
2021-08-24 08:14:34 +00:00
|
|
|
|
|
|
|
except OSError as err:
|
|
|
|
self._attr_available = False
|
2019-06-25 09:38:24 +00:00
|
|
|
_LOGGER.error("Error updating sensor: %s", err)
|
2021-09-06 10:03:45 +00:00
|
|
|
return
|
|
|
|
|
2021-09-29 15:14:41 +00:00
|
|
|
self._attr_native_value = VALLOX_PROFILE_TO_STR_REPORTABLE.get(vallox_profile)
|
2021-09-06 10:03:45 +00:00
|
|
|
self._attr_available = True
|
2019-06-25 09:38:24 +00:00
|
|
|
|
|
|
|
|
2021-09-23 17:59:28 +00:00
|
|
|
# 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.
|
2019-06-25 09:38:24 +00:00
|
|
|
#
|
2021-09-23 17:59:28 +00:00
|
|
|
# Therefore, first query the overall state of the device, and report zero percent fan speed in case
|
|
|
|
# it is not in regular operation mode.
|
2019-06-25 09:38:24 +00:00
|
|
|
class ValloxFanSpeedSensor(ValloxSensor):
|
|
|
|
"""Child class for fan speed reporting."""
|
|
|
|
|
2021-09-23 17:59:28 +00:00
|
|
|
async def async_update(self) -> None:
|
2019-06-25 09:38:24 +00:00
|
|
|
"""Fetch state from the ventilation unit."""
|
|
|
|
try:
|
2021-09-06 10:03:45 +00:00
|
|
|
fan_on = self._state_proxy.fetch_metric(METRIC_KEY_MODE) == MODE_ON
|
2019-06-25 09:38:24 +00:00
|
|
|
|
2021-09-23 17:59:28 +00:00
|
|
|
except (OSError, KeyError, TypeError) as err:
|
2021-08-24 08:14:34 +00:00
|
|
|
self._attr_available = False
|
2019-06-25 09:38:24 +00:00
|
|
|
_LOGGER.error("Error updating sensor: %s", err)
|
2021-09-06 10:03:45 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
if fan_on:
|
|
|
|
await super().async_update()
|
|
|
|
else:
|
|
|
|
# Report zero percent otherwise.
|
|
|
|
self._attr_native_value = 0
|
|
|
|
self._attr_available = True
|
2019-06-25 09:38:24 +00:00
|
|
|
|
|
|
|
|
|
|
|
class ValloxFilterRemainingSensor(ValloxSensor):
|
|
|
|
"""Child class for filter remaining time reporting."""
|
|
|
|
|
2021-09-23 17:59:28 +00:00
|
|
|
async def async_update(self) -> None:
|
2019-06-25 09:38:24 +00:00
|
|
|
"""Fetch state from the ventilation unit."""
|
2021-09-23 17:59:28 +00:00
|
|
|
await super().async_update()
|
2019-06-25 09:38:24 +00:00
|
|
|
|
2021-09-23 17:59:28 +00:00
|
|
|
# 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)):
|
2021-08-24 08:14:34 +00:00
|
|
|
self._attr_available = False
|
2021-09-23 17:59:28 +00:00
|
|
|
_LOGGER.error(
|
|
|
|
"Value has unexpected type: %s", type(self._attr_native_value)
|
|
|
|
)
|
2021-09-06 10:03:45 +00:00
|
|
|
return
|
|
|
|
|
2021-09-23 17:59:28 +00:00
|
|
|
# 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)
|
2021-09-06 10:03:45 +00:00
|
|
|
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()
|
2021-08-24 08:14:34 +00:00
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
class ValloxSensorEntityDescription(SensorEntityDescription):
|
|
|
|
"""Describes Vallox sensor entity."""
|
|
|
|
|
|
|
|
metric_key: str | None = None
|
|
|
|
sensor_type: type[ValloxSensor] = ValloxSensor
|
|
|
|
|
|
|
|
|
|
|
|
SENSORS: tuple[ValloxSensorEntityDescription, ...] = (
|
|
|
|
ValloxSensorEntityDescription(
|
|
|
|
key="current_profile",
|
|
|
|
name="Current Profile",
|
|
|
|
icon="mdi:gauge",
|
|
|
|
sensor_type=ValloxProfileSensor,
|
|
|
|
),
|
|
|
|
ValloxSensorEntityDescription(
|
|
|
|
key="fan_speed",
|
|
|
|
name="Fan Speed",
|
|
|
|
metric_key="A_CYC_FAN_SPEED",
|
|
|
|
icon="mdi:fan",
|
|
|
|
state_class=STATE_CLASS_MEASUREMENT,
|
|
|
|
native_unit_of_measurement=PERCENTAGE,
|
|
|
|
sensor_type=ValloxFanSpeedSensor,
|
|
|
|
),
|
|
|
|
ValloxSensorEntityDescription(
|
|
|
|
key="remaining_time_for_filter",
|
|
|
|
name="Remaining Time For Filter",
|
|
|
|
metric_key="A_CYC_REMAINING_TIME_FOR_FILTER",
|
|
|
|
device_class=DEVICE_CLASS_TIMESTAMP,
|
|
|
|
sensor_type=ValloxFilterRemainingSensor,
|
|
|
|
),
|
|
|
|
ValloxSensorEntityDescription(
|
|
|
|
key="extract_air",
|
|
|
|
name="Extract Air",
|
|
|
|
metric_key="A_CYC_TEMP_EXTRACT_AIR",
|
|
|
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
|
|
|
state_class=STATE_CLASS_MEASUREMENT,
|
|
|
|
native_unit_of_measurement=TEMP_CELSIUS,
|
|
|
|
),
|
|
|
|
ValloxSensorEntityDescription(
|
|
|
|
key="exhaust_air",
|
|
|
|
name="Exhaust Air",
|
|
|
|
metric_key="A_CYC_TEMP_EXHAUST_AIR",
|
|
|
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
|
|
|
state_class=STATE_CLASS_MEASUREMENT,
|
|
|
|
native_unit_of_measurement=TEMP_CELSIUS,
|
|
|
|
),
|
|
|
|
ValloxSensorEntityDescription(
|
|
|
|
key="outdoor_air",
|
|
|
|
name="Outdoor Air",
|
|
|
|
metric_key="A_CYC_TEMP_OUTDOOR_AIR",
|
|
|
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
|
|
|
state_class=STATE_CLASS_MEASUREMENT,
|
|
|
|
native_unit_of_measurement=TEMP_CELSIUS,
|
|
|
|
),
|
|
|
|
ValloxSensorEntityDescription(
|
|
|
|
key="supply_air",
|
|
|
|
name="Supply Air",
|
|
|
|
metric_key="A_CYC_TEMP_SUPPLY_AIR",
|
|
|
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
|
|
|
state_class=STATE_CLASS_MEASUREMENT,
|
|
|
|
native_unit_of_measurement=TEMP_CELSIUS,
|
|
|
|
),
|
|
|
|
ValloxSensorEntityDescription(
|
|
|
|
key="humidity",
|
|
|
|
name="Humidity",
|
|
|
|
metric_key="A_CYC_RH_VALUE",
|
|
|
|
device_class=DEVICE_CLASS_HUMIDITY,
|
|
|
|
state_class=STATE_CLASS_MEASUREMENT,
|
|
|
|
native_unit_of_measurement=PERCENTAGE,
|
|
|
|
),
|
|
|
|
ValloxSensorEntityDescription(
|
|
|
|
key="efficiency",
|
|
|
|
name="Efficiency",
|
|
|
|
metric_key="A_CYC_EXTRACT_EFFICIENCY",
|
|
|
|
icon="mdi:gauge",
|
|
|
|
state_class=STATE_CLASS_MEASUREMENT,
|
|
|
|
native_unit_of_measurement=PERCENTAGE,
|
|
|
|
),
|
|
|
|
ValloxSensorEntityDescription(
|
|
|
|
key="co2",
|
|
|
|
name="CO2",
|
|
|
|
metric_key="A_CYC_CO2_VALUE",
|
|
|
|
device_class=DEVICE_CLASS_CO2,
|
|
|
|
state_class=STATE_CLASS_MEASUREMENT,
|
|
|
|
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2021-09-23 17:59:28 +00:00
|
|
|
async def async_setup_platform(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
config: ConfigType,
|
|
|
|
async_add_entities: AddEntitiesCallback,
|
|
|
|
discovery_info: DiscoveryInfoType | None = None,
|
|
|
|
) -> None:
|
2021-08-24 08:14:34 +00:00
|
|
|
"""Set up the sensors."""
|
|
|
|
if discovery_info is None:
|
|
|
|
return
|
|
|
|
|
|
|
|
name = hass.data[DOMAIN]["name"]
|
|
|
|
state_proxy = hass.data[DOMAIN]["state_proxy"]
|
|
|
|
|
|
|
|
async_add_entities(
|
|
|
|
[
|
|
|
|
description.sensor_type(name, state_proxy, description)
|
|
|
|
for description in SENSORS
|
|
|
|
],
|
|
|
|
update_before_add=False,
|
|
|
|
)
|