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:
parent
c4f033e61c
commit
bfe21b33f0
5 changed files with 119 additions and 51 deletions
|
@ -1,17 +1,12 @@
|
||||||
"""The Flexit Nordic (BACnet) integration."""
|
"""The Flexit Nordic (BACnet) integration."""
|
||||||
from __future__ import annotations
|
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.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.core import HomeAssistant
|
||||||
from homeassistant.exceptions import ConfigEntryNotReady
|
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
|
from .coordinator import FlexitCoordinator
|
||||||
|
|
||||||
PLATFORMS: list[Platform] = [Platform.CLIMATE]
|
PLATFORMS: list[Platform] = [Platform.CLIMATE]
|
||||||
|
|
||||||
|
@ -19,24 +14,19 @@ PLATFORMS: list[Platform] = [Platform.CLIMATE]
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Set up Flexit Nordic (BACnet) from a config entry."""
|
"""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:
|
coordinator = FlexitCoordinator(hass, device_id)
|
||||||
await device.update()
|
await coordinator.async_config_entry_first_refresh()
|
||||||
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
|
|
||||||
|
|
||||||
|
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
||||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
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):
|
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||||
hass.data[DOMAIN].pop(entry.entry_id)
|
hass.data[DOMAIN].pop(entry.entry_id)
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ from flexit_bacnet import (
|
||||||
VENTILATION_MODE_AWAY,
|
VENTILATION_MODE_AWAY,
|
||||||
VENTILATION_MODE_HOME,
|
VENTILATION_MODE_HOME,
|
||||||
VENTILATION_MODE_STOP,
|
VENTILATION_MODE_STOP,
|
||||||
FlexitBACnet,
|
|
||||||
)
|
)
|
||||||
from flexit_bacnet.bacnet import DecodingError
|
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.const import ATTR_TEMPERATURE, PRECISION_HALVES, UnitOfTemperature
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers.device_registry import DeviceInfo
|
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
|
@ -32,6 +30,8 @@ from .const import (
|
||||||
PRESET_TO_VENTILATION_MODE_MAP,
|
PRESET_TO_VENTILATION_MODE_MAP,
|
||||||
VENTILATION_TO_PRESET_MODE_MAP,
|
VENTILATION_TO_PRESET_MODE_MAP,
|
||||||
)
|
)
|
||||||
|
from .coordinator import FlexitCoordinator
|
||||||
|
from .entity import FlexitEntity
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
|
@ -40,18 +40,16 @@ async def async_setup_entry(
|
||||||
async_add_devices: AddEntitiesCallback,
|
async_add_devices: AddEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the Flexit Nordic unit."""
|
"""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."""
|
"""Flexit air handling unit."""
|
||||||
|
|
||||||
_attr_name = None
|
_attr_name = None
|
||||||
|
|
||||||
_attr_has_entity_name = True
|
|
||||||
|
|
||||||
_attr_hvac_modes = [
|
_attr_hvac_modes = [
|
||||||
HVACMode.OFF,
|
HVACMode.OFF,
|
||||||
HVACMode.FAN_ONLY,
|
HVACMode.FAN_ONLY,
|
||||||
|
@ -72,36 +70,27 @@ class FlexitClimateEntity(ClimateEntity):
|
||||||
_attr_max_temp = MAX_TEMP
|
_attr_max_temp = MAX_TEMP
|
||||||
_attr_min_temp = MIN_TEMP
|
_attr_min_temp = MIN_TEMP
|
||||||
|
|
||||||
def __init__(self, device: FlexitBACnet) -> None:
|
def __init__(self, coordinator: FlexitCoordinator) -> None:
|
||||||
"""Initialize the unit."""
|
"""Initialize the Flexit unit."""
|
||||||
self._device = device
|
super().__init__(coordinator)
|
||||||
self._attr_unique_id = device.serial_number
|
self._attr_unique_id = coordinator.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,
|
|
||||||
)
|
|
||||||
|
|
||||||
async def async_update(self) -> None:
|
async def async_update(self) -> None:
|
||||||
"""Refresh unit state."""
|
"""Refresh unit state."""
|
||||||
await self._device.update()
|
await self.device.update()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_temperature(self) -> float:
|
def current_temperature(self) -> float:
|
||||||
"""Return the current temperature."""
|
"""Return the current temperature."""
|
||||||
return self._device.room_temperature
|
return self.device.room_temperature
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_temperature(self) -> float:
|
def target_temperature(self) -> float:
|
||||||
"""Return the temperature we try to reach."""
|
"""Return the temperature we try to reach."""
|
||||||
if self._device.ventilation_mode == VENTILATION_MODE_AWAY:
|
if self.device.ventilation_mode == VENTILATION_MODE_AWAY:
|
||||||
return self._device.air_temp_setpoint_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:
|
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
|
@ -109,12 +98,14 @@ class FlexitClimateEntity(ClimateEntity):
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if self._device.ventilation_mode == VENTILATION_MODE_AWAY:
|
if self.device.ventilation_mode == VENTILATION_MODE_AWAY:
|
||||||
await self._device.set_air_temp_setpoint_away(temperature)
|
await self.device.set_air_temp_setpoint_away(temperature)
|
||||||
else:
|
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:
|
except (asyncio.exceptions.TimeoutError, ConnectionError, DecodingError) as exc:
|
||||||
raise HomeAssistantError from exc
|
raise HomeAssistantError from exc
|
||||||
|
finally:
|
||||||
|
await self.coordinator.async_refresh()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def preset_mode(self) -> str:
|
def preset_mode(self) -> str:
|
||||||
|
@ -122,21 +113,23 @@ class FlexitClimateEntity(ClimateEntity):
|
||||||
|
|
||||||
Requires ClimateEntityFeature.PRESET_MODE.
|
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:
|
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||||
"""Set new preset mode."""
|
"""Set new preset mode."""
|
||||||
ventilation_mode = PRESET_TO_VENTILATION_MODE_MAP[preset_mode]
|
ventilation_mode = PRESET_TO_VENTILATION_MODE_MAP[preset_mode]
|
||||||
|
|
||||||
try:
|
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:
|
except (asyncio.exceptions.TimeoutError, ConnectionError, DecodingError) as exc:
|
||||||
raise HomeAssistantError from exc
|
raise HomeAssistantError from exc
|
||||||
|
finally:
|
||||||
|
await self.coordinator.async_refresh()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hvac_mode(self) -> HVACMode:
|
def hvac_mode(self) -> HVACMode:
|
||||||
"""Return hvac operation ie. heat, cool mode."""
|
"""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.OFF
|
||||||
|
|
||||||
return HVACMode.FAN_ONLY
|
return HVACMode.FAN_ONLY
|
||||||
|
@ -145,8 +138,10 @@ class FlexitClimateEntity(ClimateEntity):
|
||||||
"""Set new target hvac mode."""
|
"""Set new target hvac mode."""
|
||||||
try:
|
try:
|
||||||
if hvac_mode == HVACMode.OFF:
|
if hvac_mode == HVACMode.OFF:
|
||||||
await self._device.set_ventilation_mode(VENTILATION_MODE_STOP)
|
await self.device.set_ventilation_mode(VENTILATION_MODE_STOP)
|
||||||
else:
|
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:
|
except (asyncio.exceptions.TimeoutError, ConnectionError, DecodingError) as exc:
|
||||||
raise HomeAssistantError from exc
|
raise HomeAssistantError from exc
|
||||||
|
finally:
|
||||||
|
await self.coordinator.async_refresh()
|
||||||
|
|
49
homeassistant/components/flexit_bacnet/coordinator.py
Normal file
49
homeassistant/components/flexit_bacnet/coordinator.py
Normal 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
|
34
homeassistant/components/flexit_bacnet/entity.py
Normal file
34
homeassistant/components/flexit_bacnet/entity.py
Normal 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
|
|
@ -35,7 +35,7 @@ def mock_flexit_bacnet() -> Generator[AsyncMock, None, None]:
|
||||||
"homeassistant.components.flexit_bacnet.config_flow.FlexitBACnet",
|
"homeassistant.components.flexit_bacnet.config_flow.FlexitBACnet",
|
||||||
return_value=flexit_bacnet,
|
return_value=flexit_bacnet,
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.components.flexit_bacnet.FlexitBACnet",
|
"homeassistant.components.flexit_bacnet.coordinator.FlexitBACnet",
|
||||||
return_value=flexit_bacnet,
|
return_value=flexit_bacnet,
|
||||||
):
|
):
|
||||||
flexit_bacnet.serial_number = "0000-0001"
|
flexit_bacnet.serial_number = "0000-0001"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue