Add coordinator to Flexit bacnet (#108295)

* Adds coordinator and base entity class

* Patch the coordinator

* Adds device property to base class

And refactors accordingly

* Use const instead of string

* Moves _attr_has_entity_name to base entity

* Argument as positional

* Use device_id from init
This commit is contained in:
Jonas Fors Lellky 2024-01-18 15:45:56 +01:00 committed by GitHub
parent c4f033e61c
commit bfe21b33f0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 119 additions and 51 deletions

View file

@ -1,17 +1,12 @@
"""The Flexit Nordic (BACnet) integration."""
from __future__ import annotations
import asyncio.exceptions
from flexit_bacnet import FlexitBACnet
from flexit_bacnet.bacnet import DecodingError
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_DEVICE_ID, CONF_IP_ADDRESS, Platform
from homeassistant.const import CONF_DEVICE_ID, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from .const import DOMAIN
from .coordinator import FlexitCoordinator
PLATFORMS: list[Platform] = [Platform.CLIMATE]
@ -19,24 +14,19 @@ PLATFORMS: list[Platform] = [Platform.CLIMATE]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Flexit Nordic (BACnet) from a config entry."""
device = FlexitBACnet(entry.data[CONF_IP_ADDRESS], entry.data[CONF_DEVICE_ID])
device_id = entry.data[CONF_DEVICE_ID]
try:
await device.update()
except (asyncio.exceptions.TimeoutError, ConnectionError, DecodingError) as exc:
raise ConfigEntryNotReady(
f"Timeout while connecting to {entry.data[CONF_IP_ADDRESS]}"
) from exc
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = device
coordinator = FlexitCoordinator(hass, device_id)
await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
"""Unload the Flexit Nordic (BACnet) config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)

View file

@ -6,7 +6,6 @@ from flexit_bacnet import (
VENTILATION_MODE_AWAY,
VENTILATION_MODE_HOME,
VENTILATION_MODE_STOP,
FlexitBACnet,
)
from flexit_bacnet.bacnet import DecodingError
@ -22,7 +21,6 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_TEMPERATURE, PRECISION_HALVES, UnitOfTemperature
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import (
@ -32,6 +30,8 @@ from .const import (
PRESET_TO_VENTILATION_MODE_MAP,
VENTILATION_TO_PRESET_MODE_MAP,
)
from .coordinator import FlexitCoordinator
from .entity import FlexitEntity
async def async_setup_entry(
@ -40,18 +40,16 @@ async def async_setup_entry(
async_add_devices: AddEntitiesCallback,
) -> None:
"""Set up the Flexit Nordic unit."""
device = hass.data[DOMAIN][config_entry.entry_id]
coordinator: FlexitCoordinator = hass.data[DOMAIN][config_entry.entry_id]
async_add_devices([FlexitClimateEntity(device)])
async_add_devices([FlexitClimateEntity(coordinator)])
class FlexitClimateEntity(ClimateEntity):
class FlexitClimateEntity(FlexitEntity, ClimateEntity):
"""Flexit air handling unit."""
_attr_name = None
_attr_has_entity_name = True
_attr_hvac_modes = [
HVACMode.OFF,
HVACMode.FAN_ONLY,
@ -72,36 +70,27 @@ class FlexitClimateEntity(ClimateEntity):
_attr_max_temp = MAX_TEMP
_attr_min_temp = MIN_TEMP
def __init__(self, device: FlexitBACnet) -> None:
"""Initialize the unit."""
self._device = device
self._attr_unique_id = device.serial_number
self._attr_device_info = DeviceInfo(
identifiers={
(DOMAIN, device.serial_number),
},
name=device.device_name,
manufacturer="Flexit",
model="Nordic",
serial_number=device.serial_number,
)
def __init__(self, coordinator: FlexitCoordinator) -> None:
"""Initialize the Flexit unit."""
super().__init__(coordinator)
self._attr_unique_id = coordinator.device.serial_number
async def async_update(self) -> None:
"""Refresh unit state."""
await self._device.update()
await self.device.update()
@property
def current_temperature(self) -> float:
"""Return the current temperature."""
return self._device.room_temperature
return self.device.room_temperature
@property
def target_temperature(self) -> float:
"""Return the temperature we try to reach."""
if self._device.ventilation_mode == VENTILATION_MODE_AWAY:
return self._device.air_temp_setpoint_away
if self.device.ventilation_mode == VENTILATION_MODE_AWAY:
return self.device.air_temp_setpoint_away
return self._device.air_temp_setpoint_home
return self.device.air_temp_setpoint_home
async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature."""
@ -109,12 +98,14 @@ class FlexitClimateEntity(ClimateEntity):
return
try:
if self._device.ventilation_mode == VENTILATION_MODE_AWAY:
await self._device.set_air_temp_setpoint_away(temperature)
if self.device.ventilation_mode == VENTILATION_MODE_AWAY:
await self.device.set_air_temp_setpoint_away(temperature)
else:
await self._device.set_air_temp_setpoint_home(temperature)
await self.device.set_air_temp_setpoint_home(temperature)
except (asyncio.exceptions.TimeoutError, ConnectionError, DecodingError) as exc:
raise HomeAssistantError from exc
finally:
await self.coordinator.async_refresh()
@property
def preset_mode(self) -> str:
@ -122,21 +113,23 @@ class FlexitClimateEntity(ClimateEntity):
Requires ClimateEntityFeature.PRESET_MODE.
"""
return VENTILATION_TO_PRESET_MODE_MAP[self._device.ventilation_mode]
return VENTILATION_TO_PRESET_MODE_MAP[self.device.ventilation_mode]
async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set new preset mode."""
ventilation_mode = PRESET_TO_VENTILATION_MODE_MAP[preset_mode]
try:
await self._device.set_ventilation_mode(ventilation_mode)
await self.device.set_ventilation_mode(ventilation_mode)
except (asyncio.exceptions.TimeoutError, ConnectionError, DecodingError) as exc:
raise HomeAssistantError from exc
finally:
await self.coordinator.async_refresh()
@property
def hvac_mode(self) -> HVACMode:
"""Return hvac operation ie. heat, cool mode."""
if self._device.ventilation_mode == VENTILATION_MODE_STOP:
if self.device.ventilation_mode == VENTILATION_MODE_STOP:
return HVACMode.OFF
return HVACMode.FAN_ONLY
@ -145,8 +138,10 @@ class FlexitClimateEntity(ClimateEntity):
"""Set new target hvac mode."""
try:
if hvac_mode == HVACMode.OFF:
await self._device.set_ventilation_mode(VENTILATION_MODE_STOP)
await self.device.set_ventilation_mode(VENTILATION_MODE_STOP)
else:
await self._device.set_ventilation_mode(VENTILATION_MODE_HOME)
await self.device.set_ventilation_mode(VENTILATION_MODE_HOME)
except (asyncio.exceptions.TimeoutError, ConnectionError, DecodingError) as exc:
raise HomeAssistantError from exc
finally:
await self.coordinator.async_refresh()

View file

@ -0,0 +1,49 @@
"""DataUpdateCoordinator for Flexit Nordic (BACnet) integration.."""
import asyncio.exceptions
from datetime import timedelta
import logging
from flexit_bacnet import FlexitBACnet
from flexit_bacnet.bacnet import DecodingError
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_DEVICE_ID, CONF_IP_ADDRESS
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
class FlexitCoordinator(DataUpdateCoordinator[FlexitBACnet]):
"""Class to manage fetching data from a Flexit Nordic (BACnet) device."""
config_entry: ConfigEntry
def __init__(self, hass: HomeAssistant, device_id: str) -> None:
"""Initialize my coordinator."""
super().__init__(
hass,
_LOGGER,
name=f"{DOMAIN}_{device_id}",
update_interval=timedelta(seconds=60),
)
self.device = FlexitBACnet(
self.config_entry.data[CONF_IP_ADDRESS],
self.config_entry.data[CONF_DEVICE_ID],
)
async def _async_update_data(self) -> FlexitBACnet:
"""Fetch data from the device."""
try:
await self.device.update()
except (asyncio.exceptions.TimeoutError, ConnectionError, DecodingError) as exc:
raise ConfigEntryNotReady(
f"Timeout while connecting to {self.config_entry.data[CONF_IP_ADDRESS]}"
) from exc
return self.device

View file

@ -0,0 +1,34 @@
"""Base entity for the Flexit Nordic (BACnet) integration."""
from __future__ import annotations
from flexit_bacnet import FlexitBACnet
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
from .coordinator import FlexitCoordinator
class FlexitEntity(CoordinatorEntity[FlexitCoordinator]):
"""Defines a Flexit entity."""
_attr_has_entity_name = True
def __init__(self, coordinator: FlexitCoordinator) -> None:
"""Initialize a Flexit Nordic (BACnet) entity."""
super().__init__(coordinator)
self._attr_device_info = DeviceInfo(
identifiers={
(DOMAIN, coordinator.device.serial_number),
},
name=coordinator.device.device_name,
manufacturer="Flexit",
model="Nordic",
serial_number=coordinator.device.serial_number,
)
@property
def device(self) -> FlexitBACnet:
"""Return the device."""
return self.coordinator.data

View file

@ -35,7 +35,7 @@ def mock_flexit_bacnet() -> Generator[AsyncMock, None, None]:
"homeassistant.components.flexit_bacnet.config_flow.FlexitBACnet",
return_value=flexit_bacnet,
), patch(
"homeassistant.components.flexit_bacnet.FlexitBACnet",
"homeassistant.components.flexit_bacnet.coordinator.FlexitBACnet",
return_value=flexit_bacnet,
):
flexit_bacnet.serial_number = "0000-0001"