mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 16:57:53 +00:00
Split the energy and data retrieval in WeHeat (#139211)
* Split the energy and data logs * Make sure that pump_info name is set to device name, bump weheat * Adding config entry * Fixed circular import * parallelisation of awaits * Update homeassistant/components/weheat/binary_sensor.py Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> * Fix undefined weheatdata --------- Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
parent
0872243297
commit
b41fc932c5
@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from http import HTTPStatus
|
||||
|
||||
import aiohttp
|
||||
@ -18,7 +19,13 @@ from homeassistant.helpers.config_entry_oauth2_flow import (
|
||||
)
|
||||
|
||||
from .const import API_URL, LOGGER
|
||||
from .coordinator import WeheatConfigEntry, WeheatDataUpdateCoordinator
|
||||
from .coordinator import (
|
||||
HeatPumpInfo,
|
||||
WeheatConfigEntry,
|
||||
WeheatData,
|
||||
WeheatDataUpdateCoordinator,
|
||||
WeheatEnergyUpdateCoordinator,
|
||||
)
|
||||
|
||||
PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.SENSOR]
|
||||
|
||||
@ -52,14 +59,38 @@ async def async_setup_entry(hass: HomeAssistant, entry: WeheatConfigEntry) -> bo
|
||||
except UnauthorizedException as error:
|
||||
raise ConfigEntryAuthFailed from error
|
||||
|
||||
nr_of_pumps = len(discovered_heat_pumps)
|
||||
|
||||
for pump_info in discovered_heat_pumps:
|
||||
LOGGER.debug("Adding %s", pump_info)
|
||||
# for each pump, add a coordinator
|
||||
new_coordinator = WeheatDataUpdateCoordinator(hass, entry, session, pump_info)
|
||||
# for each pump, add the coordinators
|
||||
|
||||
await new_coordinator.async_config_entry_first_refresh()
|
||||
new_heat_pump = HeatPumpInfo(pump_info)
|
||||
new_data_coordinator = WeheatDataUpdateCoordinator(
|
||||
hass, entry, session, pump_info, nr_of_pumps
|
||||
)
|
||||
new_energy_coordinator = WeheatEnergyUpdateCoordinator(
|
||||
hass, entry, session, pump_info
|
||||
)
|
||||
|
||||
entry.runtime_data.append(new_coordinator)
|
||||
entry.runtime_data.append(
|
||||
WeheatData(
|
||||
heat_pump_info=new_heat_pump,
|
||||
data_coordinator=new_data_coordinator,
|
||||
energy_coordinator=new_energy_coordinator,
|
||||
)
|
||||
)
|
||||
|
||||
await asyncio.gather(
|
||||
*[
|
||||
data.data_coordinator.async_config_entry_first_refresh()
|
||||
for data in entry.runtime_data
|
||||
],
|
||||
*[
|
||||
data.energy_coordinator.async_config_entry_first_refresh()
|
||||
for data in entry.runtime_data
|
||||
],
|
||||
)
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
|
@ -14,7 +14,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
|
||||
from .coordinator import WeheatConfigEntry, WeheatDataUpdateCoordinator
|
||||
from .coordinator import HeatPumpInfo, WeheatConfigEntry, WeheatDataUpdateCoordinator
|
||||
from .entity import WeheatEntity
|
||||
|
||||
# Coordinator is used to centralize the data updates
|
||||
@ -68,10 +68,14 @@ async def async_setup_entry(
|
||||
) -> None:
|
||||
"""Set up the sensors for weheat heat pump."""
|
||||
entities = [
|
||||
WeheatHeatPumpBinarySensor(coordinator, entity_description)
|
||||
WeheatHeatPumpBinarySensor(
|
||||
weheatdata.heat_pump_info,
|
||||
weheatdata.data_coordinator,
|
||||
entity_description,
|
||||
)
|
||||
for weheatdata in entry.runtime_data
|
||||
for entity_description in BINARY_SENSORS
|
||||
for coordinator in entry.runtime_data
|
||||
if entity_description.value_fn(coordinator.data) is not None
|
||||
if entity_description.value_fn(weheatdata.data_coordinator.data) is not None
|
||||
]
|
||||
|
||||
async_add_entities(entities)
|
||||
@ -80,20 +84,21 @@ async def async_setup_entry(
|
||||
class WeheatHeatPumpBinarySensor(WeheatEntity, BinarySensorEntity):
|
||||
"""Defines a Weheat heat pump binary sensor."""
|
||||
|
||||
heat_pump_info: HeatPumpInfo
|
||||
coordinator: WeheatDataUpdateCoordinator
|
||||
entity_description: WeHeatBinarySensorEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
heat_pump_info: HeatPumpInfo,
|
||||
coordinator: WeheatDataUpdateCoordinator,
|
||||
entity_description: WeHeatBinarySensorEntityDescription,
|
||||
) -> None:
|
||||
"""Pass coordinator to CoordinatorEntity."""
|
||||
super().__init__(coordinator)
|
||||
|
||||
super().__init__(heat_pump_info, coordinator)
|
||||
self.entity_description = entity_description
|
||||
|
||||
self._attr_unique_id = f"{coordinator.heatpump_id}_{entity_description.key}"
|
||||
self._attr_unique_id = f"{heat_pump_info.heatpump_id}_{entity_description.key}"
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool | None:
|
||||
|
@ -17,7 +17,8 @@ API_URL = "https://api.weheat.nl"
|
||||
OAUTH2_SCOPES = ["openid", "offline_access"]
|
||||
|
||||
|
||||
UPDATE_INTERVAL = 30
|
||||
LOG_UPDATE_INTERVAL = 120
|
||||
ENERGY_UPDATE_INTERVAL = 1800
|
||||
|
||||
LOGGER: Logger = getLogger(__package__)
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
"""Define a custom coordinator for the Weheat heatpump integration."""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import timedelta
|
||||
|
||||
from weheat.abstractions.discovery import HeatPumpDiscovery
|
||||
@ -10,6 +11,7 @@ from weheat.exceptions import (
|
||||
ForbiddenException,
|
||||
NotFoundException,
|
||||
ServiceException,
|
||||
TooManyRequestsException,
|
||||
UnauthorizedException,
|
||||
)
|
||||
|
||||
@ -21,7 +23,9 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.config_entry_oauth2_flow import OAuth2Session
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import API_URL, DOMAIN, LOGGER, UPDATE_INTERVAL
|
||||
from .const import API_URL, DOMAIN, ENERGY_UPDATE_INTERVAL, LOG_UPDATE_INTERVAL, LOGGER
|
||||
|
||||
type WeheatConfigEntry = ConfigEntry[list[WeheatData]]
|
||||
|
||||
EXCEPTIONS = (
|
||||
ServiceException,
|
||||
@ -29,9 +33,43 @@ EXCEPTIONS = (
|
||||
ForbiddenException,
|
||||
BadRequestException,
|
||||
ApiException,
|
||||
TooManyRequestsException,
|
||||
)
|
||||
|
||||
type WeheatConfigEntry = ConfigEntry[list[WeheatDataUpdateCoordinator]]
|
||||
|
||||
class HeatPumpInfo(HeatPumpDiscovery.HeatPumpInfo):
|
||||
"""Heat pump info with additional properties."""
|
||||
|
||||
def __init__(self, pump_info: HeatPumpDiscovery.HeatPumpInfo) -> None:
|
||||
"""Initialize the HeatPump object with the provided pump information.
|
||||
|
||||
Args:
|
||||
pump_info (HeatPumpDiscovery.HeatPumpInfo): An object containing the heat pump's discovery information, including:
|
||||
- uuid (str): Unique identifier for the heat pump.
|
||||
- uuid (str): Unique identifier for the heat pump.
|
||||
- device_name (str): Name of the heat pump device.
|
||||
- model (str): Model of the heat pump.
|
||||
- sn (str): Serial number of the heat pump.
|
||||
- has_dhw (bool): Indicates if the heat pump has domestic hot water functionality.
|
||||
|
||||
"""
|
||||
super().__init__(
|
||||
pump_info.uuid,
|
||||
pump_info.device_name,
|
||||
pump_info.model,
|
||||
pump_info.sn,
|
||||
pump_info.has_dhw,
|
||||
)
|
||||
|
||||
@property
|
||||
def readable_name(self) -> str | None:
|
||||
"""Return the readable name of the heat pump."""
|
||||
return self.device_name if self.device_name else self.model
|
||||
|
||||
@property
|
||||
def heatpump_id(self) -> str:
|
||||
"""Return the heat pump id."""
|
||||
return self.uuid
|
||||
|
||||
|
||||
class WeheatDataUpdateCoordinator(DataUpdateCoordinator[HeatPump]):
|
||||
@ -45,45 +83,28 @@ class WeheatDataUpdateCoordinator(DataUpdateCoordinator[HeatPump]):
|
||||
config_entry: WeheatConfigEntry,
|
||||
session: OAuth2Session,
|
||||
heat_pump: HeatPumpDiscovery.HeatPumpInfo,
|
||||
nr_of_heat_pumps: int,
|
||||
) -> None:
|
||||
"""Initialize the data coordinator."""
|
||||
super().__init__(
|
||||
hass,
|
||||
logger=LOGGER,
|
||||
config_entry=config_entry,
|
||||
logger=LOGGER,
|
||||
name=DOMAIN,
|
||||
update_interval=timedelta(seconds=UPDATE_INTERVAL),
|
||||
update_interval=timedelta(seconds=LOG_UPDATE_INTERVAL * nr_of_heat_pumps),
|
||||
)
|
||||
self.heat_pump_info = heat_pump
|
||||
self._heat_pump_data = HeatPump(
|
||||
API_URL, heat_pump.uuid, async_get_clientsession(hass)
|
||||
)
|
||||
|
||||
self.session = session
|
||||
|
||||
@property
|
||||
def heatpump_id(self) -> str:
|
||||
"""Return the heat pump id."""
|
||||
return self.heat_pump_info.uuid
|
||||
|
||||
@property
|
||||
def readable_name(self) -> str | None:
|
||||
"""Return the readable name of the heat pump."""
|
||||
if self.heat_pump_info.name:
|
||||
return self.heat_pump_info.name
|
||||
return self.heat_pump_info.model
|
||||
|
||||
@property
|
||||
def model(self) -> str:
|
||||
"""Return the model of the heat pump."""
|
||||
return self.heat_pump_info.model
|
||||
|
||||
async def _async_update_data(self) -> HeatPump:
|
||||
"""Fetch data from the API."""
|
||||
await self.session.async_ensure_token_valid()
|
||||
|
||||
try:
|
||||
await self._heat_pump_data.async_get_status(
|
||||
await self._heat_pump_data.async_get_logs(
|
||||
self.session.token[CONF_ACCESS_TOKEN]
|
||||
)
|
||||
except UnauthorizedException as error:
|
||||
@ -92,3 +113,54 @@ class WeheatDataUpdateCoordinator(DataUpdateCoordinator[HeatPump]):
|
||||
raise UpdateFailed(error) from error
|
||||
|
||||
return self._heat_pump_data
|
||||
|
||||
|
||||
class WeheatEnergyUpdateCoordinator(DataUpdateCoordinator[HeatPump]):
|
||||
"""A custom Energy coordinator for the Weheat heatpump integration."""
|
||||
|
||||
config_entry: WeheatConfigEntry
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
config_entry: WeheatConfigEntry,
|
||||
session: OAuth2Session,
|
||||
heat_pump: HeatPumpDiscovery.HeatPumpInfo,
|
||||
) -> None:
|
||||
"""Initialize the data coordinator."""
|
||||
super().__init__(
|
||||
hass,
|
||||
config_entry=config_entry,
|
||||
logger=LOGGER,
|
||||
name=DOMAIN,
|
||||
update_interval=timedelta(seconds=ENERGY_UPDATE_INTERVAL),
|
||||
)
|
||||
self._heat_pump_data = HeatPump(
|
||||
API_URL, heat_pump.uuid, async_get_clientsession(hass)
|
||||
)
|
||||
|
||||
self.session = session
|
||||
|
||||
async def _async_update_data(self) -> HeatPump:
|
||||
"""Fetch data from the API."""
|
||||
await self.session.async_ensure_token_valid()
|
||||
|
||||
try:
|
||||
await self._heat_pump_data.async_get_energy(
|
||||
self.session.token[CONF_ACCESS_TOKEN]
|
||||
)
|
||||
except UnauthorizedException as error:
|
||||
raise ConfigEntryAuthFailed from error
|
||||
except EXCEPTIONS as error:
|
||||
raise UpdateFailed(error) from error
|
||||
|
||||
return self._heat_pump_data
|
||||
|
||||
|
||||
@dataclass
|
||||
class WeheatData:
|
||||
"""Data for the Weheat integration."""
|
||||
|
||||
heat_pump_info: HeatPumpInfo
|
||||
data_coordinator: WeheatDataUpdateCoordinator
|
||||
energy_coordinator: WeheatEnergyUpdateCoordinator
|
||||
|
@ -3,25 +3,30 @@
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from . import HeatPumpInfo
|
||||
from .const import DOMAIN, MANUFACTURER
|
||||
from .coordinator import WeheatDataUpdateCoordinator
|
||||
from .coordinator import WeheatDataUpdateCoordinator, WeheatEnergyUpdateCoordinator
|
||||
|
||||
|
||||
class WeheatEntity(CoordinatorEntity[WeheatDataUpdateCoordinator]):
|
||||
class WeheatEntity[
|
||||
_WeheatEntityT: WeheatDataUpdateCoordinator | WeheatEnergyUpdateCoordinator
|
||||
](CoordinatorEntity[_WeheatEntityT]):
|
||||
"""Defines a base Weheat entity."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: WeheatDataUpdateCoordinator,
|
||||
heat_pump_info: HeatPumpInfo,
|
||||
coordinator: _WeheatEntityT,
|
||||
) -> None:
|
||||
"""Initialize the Weheat entity."""
|
||||
super().__init__(coordinator)
|
||||
self.heat_pump_info = heat_pump_info
|
||||
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, coordinator.heatpump_id)},
|
||||
name=coordinator.readable_name,
|
||||
identifiers={(DOMAIN, heat_pump_info.heatpump_id)},
|
||||
name=heat_pump_info.readable_name,
|
||||
manufacturer=MANUFACTURER,
|
||||
model=coordinator.model,
|
||||
model=heat_pump_info.model,
|
||||
)
|
||||
|
@ -6,5 +6,5 @@
|
||||
"dependencies": ["application_credentials"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/weheat",
|
||||
"iot_class": "cloud_polling",
|
||||
"requirements": ["weheat==2025.2.22"]
|
||||
"requirements": ["weheat==2025.2.26"]
|
||||
}
|
||||
|
@ -27,7 +27,12 @@ from .const import (
|
||||
DISPLAY_PRECISION_WATER_TEMP,
|
||||
DISPLAY_PRECISION_WATTS,
|
||||
)
|
||||
from .coordinator import WeheatConfigEntry, WeheatDataUpdateCoordinator
|
||||
from .coordinator import (
|
||||
HeatPumpInfo,
|
||||
WeheatConfigEntry,
|
||||
WeheatDataUpdateCoordinator,
|
||||
WeheatEnergyUpdateCoordinator,
|
||||
)
|
||||
from .entity import WeheatEntity
|
||||
|
||||
# Coordinator is used to centralize the data updates
|
||||
@ -142,22 +147,6 @@ SENSORS = [
|
||||
else None
|
||||
),
|
||||
),
|
||||
WeHeatSensorEntityDescription(
|
||||
translation_key="electricity_used",
|
||||
key="electricity_used",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
value_fn=lambda status: status.energy_total,
|
||||
),
|
||||
WeHeatSensorEntityDescription(
|
||||
translation_key="energy_output",
|
||||
key="energy_output",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
value_fn=lambda status: status.energy_output,
|
||||
),
|
||||
WeHeatSensorEntityDescription(
|
||||
translation_key="compressor_rpm",
|
||||
key="compressor_rpm",
|
||||
@ -174,7 +163,6 @@ SENSORS = [
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
DHW_SENSORS = [
|
||||
WeHeatSensorEntityDescription(
|
||||
translation_key="dhw_top_temperature",
|
||||
@ -196,6 +184,25 @@ DHW_SENSORS = [
|
||||
),
|
||||
]
|
||||
|
||||
ENERGY_SENSORS = [
|
||||
WeHeatSensorEntityDescription(
|
||||
translation_key="electricity_used",
|
||||
key="electricity_used",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
value_fn=lambda status: status.energy_total,
|
||||
),
|
||||
WeHeatSensorEntityDescription(
|
||||
translation_key="energy_output",
|
||||
key="energy_output",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
value_fn=lambda status: status.energy_output,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
@ -203,17 +210,39 @@ async def async_setup_entry(
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the sensors for weheat heat pump."""
|
||||
entities = [
|
||||
WeheatHeatPumpSensor(coordinator, entity_description)
|
||||
for entity_description in SENSORS
|
||||
for coordinator in entry.runtime_data
|
||||
]
|
||||
entities.extend(
|
||||
WeheatHeatPumpSensor(coordinator, entity_description)
|
||||
for entity_description in DHW_SENSORS
|
||||
for coordinator in entry.runtime_data
|
||||
if coordinator.heat_pump_info.has_dhw
|
||||
)
|
||||
|
||||
entities: list[WeheatHeatPumpSensor] = []
|
||||
for weheatdata in entry.runtime_data:
|
||||
entities.extend(
|
||||
WeheatHeatPumpSensor(
|
||||
weheatdata.heat_pump_info,
|
||||
weheatdata.data_coordinator,
|
||||
entity_description,
|
||||
)
|
||||
for entity_description in SENSORS
|
||||
if entity_description.value_fn(weheatdata.data_coordinator.data) is not None
|
||||
)
|
||||
if weheatdata.heat_pump_info.has_dhw:
|
||||
entities.extend(
|
||||
WeheatHeatPumpSensor(
|
||||
weheatdata.heat_pump_info,
|
||||
weheatdata.data_coordinator,
|
||||
entity_description,
|
||||
)
|
||||
for entity_description in DHW_SENSORS
|
||||
if entity_description.value_fn(weheatdata.data_coordinator.data)
|
||||
is not None
|
||||
)
|
||||
entities.extend(
|
||||
WeheatHeatPumpSensor(
|
||||
weheatdata.heat_pump_info,
|
||||
weheatdata.energy_coordinator,
|
||||
entity_description,
|
||||
)
|
||||
for entity_description in ENERGY_SENSORS
|
||||
if entity_description.value_fn(weheatdata.energy_coordinator.data)
|
||||
is not None
|
||||
)
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
@ -221,20 +250,21 @@ async def async_setup_entry(
|
||||
class WeheatHeatPumpSensor(WeheatEntity, SensorEntity):
|
||||
"""Defines a Weheat heat pump sensor."""
|
||||
|
||||
coordinator: WeheatDataUpdateCoordinator
|
||||
heat_pump_info: HeatPumpInfo
|
||||
coordinator: WeheatDataUpdateCoordinator | WeheatEnergyUpdateCoordinator
|
||||
entity_description: WeHeatSensorEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: WeheatDataUpdateCoordinator,
|
||||
heat_pump_info: HeatPumpInfo,
|
||||
coordinator: WeheatDataUpdateCoordinator | WeheatEnergyUpdateCoordinator,
|
||||
entity_description: WeHeatSensorEntityDescription,
|
||||
) -> None:
|
||||
"""Pass coordinator to CoordinatorEntity."""
|
||||
super().__init__(coordinator)
|
||||
|
||||
super().__init__(heat_pump_info, coordinator)
|
||||
self.entity_description = entity_description
|
||||
|
||||
self._attr_unique_id = f"{coordinator.heatpump_id}_{entity_description.key}"
|
||||
self._attr_unique_id = f"{heat_pump_info.heatpump_id}_{entity_description.key}"
|
||||
|
||||
@property
|
||||
def native_value(self) -> StateType:
|
||||
|
2
requirements_all.txt
generated
2
requirements_all.txt
generated
@ -3058,7 +3058,7 @@ webio-api==0.1.11
|
||||
webmin-xmlrpc==0.0.2
|
||||
|
||||
# homeassistant.components.weheat
|
||||
weheat==2025.2.22
|
||||
weheat==2025.2.26
|
||||
|
||||
# homeassistant.components.whirlpool
|
||||
whirlpool-sixth-sense==0.18.12
|
||||
|
2
requirements_test_all.txt
generated
2
requirements_test_all.txt
generated
@ -2462,7 +2462,7 @@ webio-api==0.1.11
|
||||
webmin-xmlrpc==0.0.2
|
||||
|
||||
# homeassistant.components.weheat
|
||||
weheat==2025.2.22
|
||||
weheat==2025.2.26
|
||||
|
||||
# homeassistant.components.whirlpool
|
||||
whirlpool-sixth-sense==0.18.12
|
||||
|
Loading…
x
Reference in New Issue
Block a user