2019-06-25 09:38:24 +00:00
|
|
|
"""Support for Vallox ventilation unit sensors."""
|
|
|
|
|
|
|
|
from datetime import datetime, timedelta
|
|
|
|
import logging
|
|
|
|
|
|
|
|
from homeassistant.const import (
|
2019-07-31 19:25:30 +00:00
|
|
|
DEVICE_CLASS_HUMIDITY,
|
|
|
|
DEVICE_CLASS_TEMPERATURE,
|
|
|
|
DEVICE_CLASS_TIMESTAMP,
|
|
|
|
TEMP_CELSIUS,
|
2020-02-28 19:46:48 +00:00
|
|
|
UNIT_PERCENTAGE,
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2019-06-25 09:38:24 +00:00
|
|
|
from homeassistant.core import callback
|
|
|
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
|
|
|
from homeassistant.helpers.entity import Entity
|
|
|
|
|
|
|
|
from . import DOMAIN, METRIC_KEY_MODE, SIGNAL_VALLOX_STATE_UPDATE
|
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
2019-06-25 09:38:24 +00:00
|
|
|
"""Set up the sensors."""
|
|
|
|
if discovery_info is None:
|
|
|
|
return
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
name = hass.data[DOMAIN]["name"]
|
|
|
|
state_proxy = hass.data[DOMAIN]["state_proxy"]
|
2019-06-25 09:38:24 +00:00
|
|
|
|
|
|
|
sensors = [
|
|
|
|
ValloxProfileSensor(
|
2019-09-03 19:12:51 +00:00
|
|
|
name=f"{name} Current Profile",
|
2019-06-25 09:38:24 +00:00
|
|
|
state_proxy=state_proxy,
|
|
|
|
device_class=None,
|
|
|
|
unit_of_measurement=None,
|
2019-07-31 19:25:30 +00:00
|
|
|
icon="mdi:gauge",
|
2019-06-25 09:38:24 +00:00
|
|
|
),
|
|
|
|
ValloxFanSpeedSensor(
|
2019-09-03 19:12:51 +00:00
|
|
|
name=f"{name} Fan Speed",
|
2019-06-25 09:38:24 +00:00
|
|
|
state_proxy=state_proxy,
|
2019-07-31 19:25:30 +00:00
|
|
|
metric_key="A_CYC_FAN_SPEED",
|
2019-06-25 09:38:24 +00:00
|
|
|
device_class=None,
|
2020-02-28 19:46:48 +00:00
|
|
|
unit_of_measurement=UNIT_PERCENTAGE,
|
2019-07-31 19:25:30 +00:00
|
|
|
icon="mdi:fan",
|
2019-06-25 09:38:24 +00:00
|
|
|
),
|
|
|
|
ValloxSensor(
|
2019-09-03 19:12:51 +00:00
|
|
|
name=f"{name} Extract Air",
|
2019-06-25 09:38:24 +00:00
|
|
|
state_proxy=state_proxy,
|
2019-07-31 19:25:30 +00:00
|
|
|
metric_key="A_CYC_TEMP_EXTRACT_AIR",
|
2019-06-25 09:38:24 +00:00
|
|
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
|
|
|
unit_of_measurement=TEMP_CELSIUS,
|
2019-07-31 19:25:30 +00:00
|
|
|
icon=None,
|
2019-06-25 09:38:24 +00:00
|
|
|
),
|
|
|
|
ValloxSensor(
|
2019-09-03 19:12:51 +00:00
|
|
|
name=f"{name} Exhaust Air",
|
2019-06-25 09:38:24 +00:00
|
|
|
state_proxy=state_proxy,
|
2019-07-31 19:25:30 +00:00
|
|
|
metric_key="A_CYC_TEMP_EXHAUST_AIR",
|
2019-06-25 09:38:24 +00:00
|
|
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
|
|
|
unit_of_measurement=TEMP_CELSIUS,
|
2019-07-31 19:25:30 +00:00
|
|
|
icon=None,
|
2019-06-25 09:38:24 +00:00
|
|
|
),
|
|
|
|
ValloxSensor(
|
2019-09-03 19:12:51 +00:00
|
|
|
name=f"{name} Outdoor Air",
|
2019-06-25 09:38:24 +00:00
|
|
|
state_proxy=state_proxy,
|
2019-07-31 19:25:30 +00:00
|
|
|
metric_key="A_CYC_TEMP_OUTDOOR_AIR",
|
2019-06-25 09:38:24 +00:00
|
|
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
|
|
|
unit_of_measurement=TEMP_CELSIUS,
|
2019-07-31 19:25:30 +00:00
|
|
|
icon=None,
|
2019-06-25 09:38:24 +00:00
|
|
|
),
|
|
|
|
ValloxSensor(
|
2019-09-03 19:12:51 +00:00
|
|
|
name=f"{name} Supply Air",
|
2019-06-25 09:38:24 +00:00
|
|
|
state_proxy=state_proxy,
|
2019-07-31 19:25:30 +00:00
|
|
|
metric_key="A_CYC_TEMP_SUPPLY_AIR",
|
2019-06-25 09:38:24 +00:00
|
|
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
|
|
|
unit_of_measurement=TEMP_CELSIUS,
|
2019-07-31 19:25:30 +00:00
|
|
|
icon=None,
|
2019-06-25 09:38:24 +00:00
|
|
|
),
|
|
|
|
ValloxSensor(
|
2019-09-03 19:12:51 +00:00
|
|
|
name=f"{name} Humidity",
|
2019-06-25 09:38:24 +00:00
|
|
|
state_proxy=state_proxy,
|
2019-07-31 19:25:30 +00:00
|
|
|
metric_key="A_CYC_RH_VALUE",
|
2019-06-25 09:38:24 +00:00
|
|
|
device_class=DEVICE_CLASS_HUMIDITY,
|
2020-02-28 19:46:48 +00:00
|
|
|
unit_of_measurement=UNIT_PERCENTAGE,
|
2019-07-31 19:25:30 +00:00
|
|
|
icon=None,
|
2019-06-25 09:38:24 +00:00
|
|
|
),
|
|
|
|
ValloxFilterRemainingSensor(
|
2019-09-03 19:12:51 +00:00
|
|
|
name=f"{name} Remaining Time For Filter",
|
2019-06-25 09:38:24 +00:00
|
|
|
state_proxy=state_proxy,
|
2019-07-31 19:25:30 +00:00
|
|
|
metric_key="A_CYC_REMAINING_TIME_FOR_FILTER",
|
2019-06-25 09:38:24 +00:00
|
|
|
device_class=DEVICE_CLASS_TIMESTAMP,
|
|
|
|
unit_of_measurement=None,
|
2019-07-31 19:25:30 +00:00
|
|
|
icon="mdi:filter",
|
2019-06-25 09:38:24 +00:00
|
|
|
),
|
|
|
|
]
|
|
|
|
|
2019-07-23 21:32:48 +00:00
|
|
|
async_add_entities(sensors, update_before_add=False)
|
2019-06-25 09:38:24 +00:00
|
|
|
|
|
|
|
|
|
|
|
class ValloxSensor(Entity):
|
|
|
|
"""Representation of a Vallox sensor."""
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
def __init__(
|
|
|
|
self, name, state_proxy, metric_key, device_class, unit_of_measurement, icon
|
|
|
|
) -> None:
|
2019-06-25 09:38:24 +00:00
|
|
|
"""Initialize the Vallox sensor."""
|
|
|
|
self._name = name
|
|
|
|
self._state_proxy = state_proxy
|
|
|
|
self._metric_key = metric_key
|
|
|
|
self._device_class = device_class
|
|
|
|
self._unit_of_measurement = unit_of_measurement
|
|
|
|
self._icon = icon
|
|
|
|
self._available = None
|
|
|
|
self._state = None
|
|
|
|
|
|
|
|
@property
|
|
|
|
def should_poll(self):
|
|
|
|
"""Do not poll the device."""
|
|
|
|
return False
|
|
|
|
|
|
|
|
@property
|
|
|
|
def name(self):
|
|
|
|
"""Return the name."""
|
|
|
|
return self._name
|
|
|
|
|
|
|
|
@property
|
|
|
|
def unit_of_measurement(self):
|
|
|
|
"""Return the unit of measurement."""
|
|
|
|
return self._unit_of_measurement
|
|
|
|
|
|
|
|
@property
|
|
|
|
def device_class(self):
|
|
|
|
"""Return the device class."""
|
|
|
|
return self._device_class
|
|
|
|
|
|
|
|
@property
|
|
|
|
def icon(self):
|
|
|
|
"""Return the icon."""
|
|
|
|
return self._icon
|
|
|
|
|
|
|
|
@property
|
|
|
|
def available(self):
|
|
|
|
"""Return true when state is known."""
|
|
|
|
return self._available
|
|
|
|
|
|
|
|
@property
|
|
|
|
def state(self):
|
|
|
|
"""Return the state."""
|
|
|
|
return self._state
|
|
|
|
|
|
|
|
async def async_added_to_hass(self):
|
|
|
|
"""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
|
|
|
|
def _update_callback(self):
|
|
|
|
"""Call update method."""
|
|
|
|
self.async_schedule_update_ha_state(True)
|
|
|
|
|
|
|
|
async def async_update(self):
|
|
|
|
"""Fetch state from the ventilation unit."""
|
|
|
|
try:
|
|
|
|
self._state = self._state_proxy.fetch_metric(self._metric_key)
|
|
|
|
self._available = True
|
|
|
|
|
|
|
|
except (OSError, KeyError) as err:
|
|
|
|
self._available = False
|
|
|
|
_LOGGER.error("Error updating sensor: %s", err)
|
|
|
|
|
|
|
|
|
|
|
|
# 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.
|
|
|
|
#
|
|
|
|
# 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):
|
|
|
|
"""Fetch state from the ventilation unit."""
|
|
|
|
try:
|
|
|
|
# If device is in regular operation, continue.
|
|
|
|
if self._state_proxy.fetch_metric(METRIC_KEY_MODE) == 0:
|
|
|
|
await super().async_update()
|
|
|
|
else:
|
|
|
|
# Report zero percent otherwise.
|
|
|
|
self._state = 0
|
|
|
|
self._available = True
|
|
|
|
|
|
|
|
except (OSError, KeyError) as err:
|
|
|
|
self._available = False
|
|
|
|
_LOGGER.error("Error updating sensor: %s", err)
|
|
|
|
|
|
|
|
|
|
|
|
class ValloxProfileSensor(ValloxSensor):
|
|
|
|
"""Child class for profile reporting."""
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
def __init__(
|
|
|
|
self, name, state_proxy, device_class, unit_of_measurement, icon
|
|
|
|
) -> None:
|
2019-06-25 09:38:24 +00:00
|
|
|
"""Initialize the Vallox sensor."""
|
2019-07-31 19:25:30 +00:00
|
|
|
super().__init__(
|
|
|
|
name, state_proxy, None, device_class, unit_of_measurement, icon
|
|
|
|
)
|
2019-06-25 09:38:24 +00:00
|
|
|
|
|
|
|
async def async_update(self):
|
|
|
|
"""Fetch state from the ventilation unit."""
|
|
|
|
try:
|
|
|
|
self._state = self._state_proxy.get_profile()
|
|
|
|
self._available = True
|
|
|
|
|
|
|
|
except OSError as err:
|
|
|
|
self._available = False
|
|
|
|
_LOGGER.error("Error updating sensor: %s", err)
|
|
|
|
|
|
|
|
|
|
|
|
class ValloxFilterRemainingSensor(ValloxSensor):
|
|
|
|
"""Child class for filter remaining time reporting."""
|
|
|
|
|
|
|
|
async def async_update(self):
|
|
|
|
"""Fetch state from the ventilation unit."""
|
|
|
|
try:
|
2019-07-31 19:25:30 +00:00
|
|
|
days_remaining = int(self._state_proxy.fetch_metric(self._metric_key))
|
2019-06-25 09:38:24 +00:00
|
|
|
days_remaining_delta = timedelta(days=days_remaining)
|
|
|
|
|
|
|
|
# Since only a delta of days is received from the device, fix the
|
|
|
|
# time so the timestamp does not change with every update.
|
2019-07-31 19:25:30 +00:00
|
|
|
now = datetime.utcnow().replace(hour=13, minute=0, second=0, microsecond=0)
|
2019-06-25 09:38:24 +00:00
|
|
|
|
|
|
|
self._state = (now + days_remaining_delta).isoformat()
|
|
|
|
self._available = True
|
|
|
|
|
|
|
|
except (OSError, KeyError) as err:
|
|
|
|
self._available = False
|
|
|
|
_LOGGER.error("Error updating sensor: %s", err)
|