Add coordinator to Smarty (#129083)

* Add coordinator to Smarty

* Add coordinator to Smarty

* Fix
This commit is contained in:
Joost Lekkerkerker 2024-10-24 22:41:21 +02:00 committed by GitHub
parent 1c5193aa4d
commit 6df2c0bab5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 159 additions and 179 deletions

View file

@ -1,23 +1,20 @@
"""Support to control a Salda Smarty XP/XV ventilation unit."""
from datetime import timedelta
import ipaddress
import logging
from pysmarty2 import Smarty
import voluptuous as vol
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import CONF_HOST, CONF_NAME, Platform
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers import issue_registry as ir
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.typing import ConfigType
from .const import DOMAIN, SIGNAL_UPDATE_SMARTY
from .const import DOMAIN
from .coordinator import SmartyConfigEntry, SmartyCoordinator
_LOGGER = logging.getLogger(__name__)
@ -35,8 +32,6 @@ CONFIG_SCHEMA = vol.Schema(
PLATFORMS = [Platform.BINARY_SENSOR, Platform.FAN, Platform.SENSOR]
type SmartyConfigEntry = ConfigEntry[Smarty]
async def async_setup(hass: HomeAssistant, hass_config: ConfigType) -> bool:
"""Create a smarty system."""
@ -89,27 +84,11 @@ async def _async_import(hass: HomeAssistant, config: ConfigType) -> None:
async def async_setup_entry(hass: HomeAssistant, entry: SmartyConfigEntry) -> bool:
"""Set up the Smarty environment from a config entry."""
def _setup_smarty() -> Smarty:
smarty = Smarty(host=entry.data[CONF_HOST])
smarty.update()
return smarty
coordinator = SmartyCoordinator(hass)
smarty = await hass.async_add_executor_job(_setup_smarty)
await coordinator.async_config_entry_first_refresh()
entry.runtime_data = smarty
async def poll_device_update(event_time) -> None:
"""Update Smarty device."""
_LOGGER.debug("Updating Smarty device")
if await hass.async_add_executor_job(smarty.update):
_LOGGER.debug("Update success")
async_dispatcher_send(hass, SIGNAL_UPDATE_SMARTY)
else:
_LOGGER.debug("Update failed")
entry.async_on_unload(
async_track_time_interval(hass, poll_device_update, timedelta(seconds=30))
)
entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

View file

@ -4,17 +4,15 @@ from __future__ import annotations
import logging
from pysmarty2 import Smarty
from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
BinarySensorEntity,
)
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.update_coordinator import CoordinatorEntity
from . import SIGNAL_UPDATE_SMARTY, SmartyConfigEntry
from .coordinator import SmartyConfigEntry, SmartyCoordinator
_LOGGER = logging.getLogger(__name__)
@ -26,88 +24,76 @@ async def async_setup_entry(
) -> None:
"""Set up the Smarty Binary Sensor Platform."""
smarty = entry.runtime_data
entry_id = entry.entry_id
coordinator = entry.runtime_data
sensors = [
AlarmSensor(entry.title, smarty, entry_id),
WarningSensor(entry.title, smarty, entry_id),
BoostSensor(entry.title, smarty, entry_id),
AlarmSensor(coordinator),
WarningSensor(coordinator),
BoostSensor(coordinator),
]
async_add_entities(sensors, True)
async_add_entities(sensors)
class SmartyBinarySensor(BinarySensorEntity):
class SmartyBinarySensor(CoordinatorEntity[SmartyCoordinator], BinarySensorEntity):
"""Representation of a Smarty Binary Sensor."""
_attr_should_poll = False
def __init__(
self,
coordinator: SmartyCoordinator,
name: str,
device_class: BinarySensorDeviceClass | None,
smarty: Smarty,
) -> None:
"""Initialize the entity."""
self._attr_name = name
super().__init__(coordinator)
self._attr_name = f"{coordinator.config_entry.title} {name}"
self._attr_device_class = device_class
self._smarty = smarty
async def async_added_to_hass(self) -> None:
"""Call to update."""
async_dispatcher_connect(self.hass, SIGNAL_UPDATE_SMARTY, self._update_callback)
@callback
def _update_callback(self) -> None:
"""Call update method."""
self.async_schedule_update_ha_state(True)
class BoostSensor(SmartyBinarySensor):
"""Boost State Binary Sensor."""
def __init__(self, name: str, smarty: Smarty, entry_id: str) -> None:
def __init__(self, coordinator: SmartyCoordinator) -> None:
"""Alarm Sensor Init."""
super().__init__(name=f"{name} Boost State", device_class=None, smarty=smarty)
self._attr_unique_id = f"{entry_id}_boost"
super().__init__(coordinator, name="Boost State", device_class=None)
self._attr_unique_id = f"{coordinator.config_entry.entry_id}_boost"
def update(self) -> None:
"""Update state."""
_LOGGER.debug("Updating sensor %s", self._attr_name)
self._attr_is_on = self._smarty.boost
@property
def is_on(self) -> bool | None:
"""Return true if the binary sensor is on."""
return self.coordinator.client.boost
class AlarmSensor(SmartyBinarySensor):
"""Alarm Binary Sensor."""
def __init__(self, name: str, smarty: Smarty, entry_id: str) -> None:
def __init__(self, coordinator: SmartyCoordinator) -> None:
"""Alarm Sensor Init."""
super().__init__(
name=f"{name} Alarm",
coordinator,
name="Alarm",
device_class=BinarySensorDeviceClass.PROBLEM,
smarty=smarty,
)
self._attr_unique_id = f"{entry_id}_alarm"
self._attr_unique_id = f"{coordinator.config_entry.entry_id}_alarm"
def update(self) -> None:
"""Update state."""
_LOGGER.debug("Updating sensor %s", self._attr_name)
self._attr_is_on = self._smarty.alarm
@property
def is_on(self) -> bool | None:
"""Return true if the binary sensor is on."""
return self.coordinator.client.alarm
class WarningSensor(SmartyBinarySensor):
"""Warning Sensor."""
def __init__(self, name: str, smarty: Smarty, entry_id: str) -> None:
def __init__(self, coordinator: SmartyCoordinator) -> None:
"""Warning Sensor Init."""
super().__init__(
name=f"{name} Warning",
coordinator,
name="Warning",
device_class=BinarySensorDeviceClass.PROBLEM,
smarty=smarty,
)
self._attr_unique_id = f"{entry_id}_warning"
self._attr_unique_id = f"{coordinator.config_entry.entry_id}_warning"
def update(self) -> None:
"""Update state."""
_LOGGER.debug("Updating sensor %s", self._attr_name)
self._attr_is_on = self._smarty.warning
@property
def is_on(self) -> bool | None:
"""Return true if the binary sensor is on."""
return self.coordinator.client.warning

View file

@ -1,5 +1,3 @@
"""Constants for the Smarty component."""
DOMAIN = "smarty"
SIGNAL_UPDATE_SMARTY = "smarty_update"

View file

@ -0,0 +1,36 @@
"""Smarty Coordinator."""
from datetime import timedelta
import logging
from pysmarty2 import Smarty
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
_LOGGER = logging.getLogger(__name__)
type SmartyConfigEntry = ConfigEntry[SmartyCoordinator]
class SmartyCoordinator(DataUpdateCoordinator[None]):
"""Smarty Coordinator."""
config_entry: SmartyConfigEntry
def __init__(self, hass: HomeAssistant) -> None:
"""Initialize."""
super().__init__(
hass,
logger=_LOGGER,
name="Smarty",
update_interval=timedelta(seconds=30),
)
self.client = Smarty(host=self.config_entry.data[CONF_HOST])
async def _async_update_data(self) -> None:
"""Fetch data from Smarty."""
if not await self.hass.async_add_executor_job(self.client.update):
raise UpdateFailed("Failed to update Smarty data")

View file

@ -9,15 +9,16 @@ from typing import Any
from homeassistant.components.fan import FanEntity, FanEntityFeature
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util.percentage import (
percentage_to_ranged_value,
ranged_value_to_percentage,
)
from homeassistant.util.scaling import int_states_in_range
from . import SIGNAL_UPDATE_SMARTY, SmartyConfigEntry
from . import SmartyConfigEntry
from .coordinator import SmartyCoordinator
_LOGGER = logging.getLogger(__name__)
@ -32,16 +33,15 @@ async def async_setup_entry(
) -> None:
"""Set up the Smarty Fan Platform."""
smarty = entry.runtime_data
coordinator = entry.runtime_data
async_add_entities([SmartyFan(entry.title, smarty, entry.entry_id)], True)
async_add_entities([SmartyFan(coordinator)])
class SmartyFan(FanEntity):
class SmartyFan(CoordinatorEntity[SmartyCoordinator], FanEntity):
"""Representation of a Smarty Fan."""
_attr_icon = "mdi:air-conditioner"
_attr_should_poll = False
_attr_supported_features = (
FanEntityFeature.SET_SPEED
| FanEntityFeature.TURN_OFF
@ -49,12 +49,13 @@ class SmartyFan(FanEntity):
)
_enable_turn_on_off_backwards_compatibility = False
def __init__(self, name, smarty, entry_id):
def __init__(self, coordinator: SmartyCoordinator) -> None:
"""Initialize the entity."""
self._attr_name = name
super().__init__(coordinator)
self._attr_name = coordinator.config_entry.title
self._smarty_fan_speed = 0
self._smarty = smarty
self._attr_unique_id = entry_id
self._smarty = coordinator.client
self._attr_unique_id = coordinator.config_entry.entry_id
@property
def is_on(self) -> bool:
@ -108,17 +109,8 @@ class SmartyFan(FanEntity):
self._smarty_fan_speed = 0
self.schedule_update_ha_state()
async def async_added_to_hass(self) -> None:
"""Call to update fan."""
self.async_on_remove(
async_dispatcher_connect(
self.hass, SIGNAL_UPDATE_SMARTY, self._update_callback
)
)
@callback
def _update_callback(self) -> None:
def _handle_coordinator_update(self) -> None:
"""Call update method."""
_LOGGER.debug("Updating state")
self._smarty_fan_speed = self._smarty.fan_speed
self.async_write_ha_state()
super()._handle_coordinator_update()

View file

@ -2,19 +2,17 @@
from __future__ import annotations
import datetime as dt
from datetime import datetime, timedelta
import logging
from pysmarty2 import Smarty
from homeassistant.components.sensor import SensorDeviceClass, SensorEntity
from homeassistant.const import UnitOfTemperature
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.update_coordinator import CoordinatorEntity
import homeassistant.util.dt as dt_util
from . import SIGNAL_UPDATE_SMARTY, SmartyConfigEntry
from .coordinator import SmartyConfigEntry, SmartyCoordinator
_LOGGER = logging.getLogger(__name__)
@ -26,162 +24,153 @@ async def async_setup_entry(
) -> None:
"""Set up the Smarty Sensor Platform."""
smarty = entry.runtime_data
entry_id = entry.entry_id
coordinator = entry.runtime_data
sensors = [
SupplyAirTemperatureSensor(entry.title, smarty, entry_id),
ExtractAirTemperatureSensor(entry.title, smarty, entry_id),
OutdoorAirTemperatureSensor(entry.title, smarty, entry_id),
SupplyFanSpeedSensor(entry.title, smarty, entry_id),
ExtractFanSpeedSensor(entry.title, smarty, entry_id),
FilterDaysLeftSensor(entry.title, smarty, entry_id),
SupplyAirTemperatureSensor(coordinator),
ExtractAirTemperatureSensor(coordinator),
OutdoorAirTemperatureSensor(coordinator),
SupplyFanSpeedSensor(coordinator),
ExtractFanSpeedSensor(coordinator),
FilterDaysLeftSensor(coordinator),
]
async_add_entities(sensors, True)
async_add_entities(sensors)
class SmartySensor(SensorEntity):
class SmartySensor(CoordinatorEntity[SmartyCoordinator], SensorEntity):
"""Representation of a Smarty Sensor."""
_attr_should_poll = False
def __init__(
self,
coordinator: SmartyCoordinator,
name: str,
key: str,
device_class: SensorDeviceClass | None,
smarty: Smarty,
unit_of_measurement: str | None,
) -> None:
"""Initialize the entity."""
self._attr_name = name
super().__init__(coordinator)
self._attr_name = f"{coordinator.config_entry.title} {name}"
self._attr_unique_id = f"{coordinator.config_entry.entry_id}_{key}"
self._attr_native_value = None
self._attr_device_class = device_class
self._attr_native_unit_of_measurement = unit_of_measurement
self._smarty = smarty
async def async_added_to_hass(self) -> None:
"""Call to update."""
async_dispatcher_connect(self.hass, SIGNAL_UPDATE_SMARTY, self._update_callback)
@callback
def _update_callback(self) -> None:
"""Call update method."""
self.async_schedule_update_ha_state(True)
class SupplyAirTemperatureSensor(SmartySensor):
"""Supply Air Temperature Sensor."""
def __init__(self, name: str, smarty: Smarty, entry_id: str) -> None:
def __init__(self, coordinator: SmartyCoordinator) -> None:
"""Supply Air Temperature Init."""
super().__init__(
name=f"{name} Supply Air Temperature",
coordinator,
name="Supply Air Temperature",
key="supply_air_temperature",
device_class=SensorDeviceClass.TEMPERATURE,
unit_of_measurement=UnitOfTemperature.CELSIUS,
smarty=smarty,
)
self._attr_unique_id = f"{entry_id}_supply_air_temperature"
def update(self) -> None:
"""Update state."""
_LOGGER.debug("Updating sensor %s", self._attr_name)
self._attr_native_value = self._smarty.supply_air_temperature
@property
def native_value(self) -> float | None:
"""Return the state of the sensor."""
return self.coordinator.client.supply_air_temperature
class ExtractAirTemperatureSensor(SmartySensor):
"""Extract Air Temperature Sensor."""
def __init__(self, name: str, smarty: Smarty, entry_id: str) -> None:
def __init__(self, coordinator: SmartyCoordinator) -> None:
"""Supply Air Temperature Init."""
super().__init__(
name=f"{name} Extract Air Temperature",
coordinator,
name="Extract Air Temperature",
key="extract_air_temperature",
device_class=SensorDeviceClass.TEMPERATURE,
unit_of_measurement=UnitOfTemperature.CELSIUS,
smarty=smarty,
)
self._attr_unique_id = f"{entry_id}_extract_air_temperature"
def update(self) -> None:
"""Update state."""
_LOGGER.debug("Updating sensor %s", self._attr_name)
self._attr_native_value = self._smarty.extract_air_temperature
@property
def native_value(self) -> float | None:
"""Return the state of the sensor."""
return self.coordinator.client.extract_air_temperature
class OutdoorAirTemperatureSensor(SmartySensor):
"""Extract Air Temperature Sensor."""
def __init__(self, name: str, smarty: Smarty, entry_id: str) -> None:
def __init__(self, coordinator: SmartyCoordinator) -> None:
"""Outdoor Air Temperature Init."""
super().__init__(
name=f"{name} Outdoor Air Temperature",
coordinator,
name="Outdoor Air Temperature",
key="outdoor_air_temperature",
device_class=SensorDeviceClass.TEMPERATURE,
unit_of_measurement=UnitOfTemperature.CELSIUS,
smarty=smarty,
)
self._attr_unique_id = f"{entry_id}_outdoor_air_temperature"
def update(self) -> None:
"""Update state."""
_LOGGER.debug("Updating sensor %s", self._attr_name)
self._attr_native_value = self._smarty.outdoor_air_temperature
@property
def native_value(self) -> float | None:
"""Return the state of the sensor."""
return self.coordinator.client.outdoor_air_temperature
class SupplyFanSpeedSensor(SmartySensor):
"""Supply Fan Speed RPM."""
def __init__(self, name: str, smarty: Smarty, entry_id: str) -> None:
def __init__(self, coordinator: SmartyCoordinator) -> None:
"""Supply Fan Speed RPM Init."""
super().__init__(
name=f"{name} Supply Fan Speed",
coordinator,
name="Supply Fan Speed",
key="supply_fan_speed",
device_class=None,
unit_of_measurement=None,
smarty=smarty,
)
self._attr_unique_id = f"{entry_id}_supply_fan_speed"
def update(self) -> None:
"""Update state."""
_LOGGER.debug("Updating sensor %s", self._attr_name)
self._attr_native_value = self._smarty.supply_fan_speed
@property
def native_value(self) -> float | None:
"""Return the state of the sensor."""
return self.coordinator.client.supply_fan_speed
class ExtractFanSpeedSensor(SmartySensor):
"""Extract Fan Speed RPM."""
def __init__(self, name: str, smarty: Smarty, entry_id: str) -> None:
def __init__(self, coordinator: SmartyCoordinator) -> None:
"""Extract Fan Speed RPM Init."""
super().__init__(
name=f"{name} Extract Fan Speed",
coordinator,
name="Extract Fan Speed",
key="extract_fan_speed",
device_class=None,
unit_of_measurement=None,
smarty=smarty,
)
self._attr_unique_id = f"{entry_id}_extract_fan_speed"
def update(self) -> None:
"""Update state."""
_LOGGER.debug("Updating sensor %s", self._attr_name)
self._attr_native_value = self._smarty.extract_fan_speed
@property
def native_value(self) -> float | None:
"""Return the state of the sensor."""
return self.coordinator.client.extract_fan_speed
class FilterDaysLeftSensor(SmartySensor):
"""Filter Days Left."""
def __init__(self, name: str, smarty: Smarty, entry_id: str) -> None:
def __init__(self, coordinator: SmartyCoordinator) -> None:
"""Filter Days Left Init."""
super().__init__(
name=f"{name} Filter Days Left",
coordinator,
name="Filter Days Left",
key="filter_days_left",
device_class=SensorDeviceClass.TIMESTAMP,
unit_of_measurement=None,
smarty=smarty,
)
self._days_left = 91
self._attr_unique_id = f"{entry_id}_filter_days_left"
def update(self) -> None:
"""Update state."""
_LOGGER.debug("Updating sensor %s", self._attr_name)
days_left = self._smarty.filter_timer
@property
def native_value(self) -> datetime | None:
"""Return the state of the sensor."""
days_left = self.coordinator.client.filter_timer
if days_left is not None and days_left != self._days_left:
self._attr_native_value = dt_util.now() + dt.timedelta(days=days_left)
self._days_left = days_left
return dt_util.now() + timedelta(days=days_left)
return None

View file

@ -27,7 +27,7 @@ def mock_smarty() -> Generator[AsyncMock]:
"""Mock a Smarty client."""
with (
patch(
"homeassistant.components.smarty.Smarty",
"homeassistant.components.smarty.coordinator.Smarty",
autospec=True,
) as mock_client,
patch(