Skip to content
Snippets Groups Projects
Unverified Commit b41fc932 authored by SteveDiks's avatar SteveDiks Committed by Franck Nijhof
Browse files

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: default avatarJoost Lekkerkerker <joostlek@outlook.com>

* Fix undefined weheatdata

---------

Co-authored-by: default avatarJoost Lekkerkerker <joostlek@outlook.com>
parent 08722432
No related branches found
No related tags found
No related merge requests found
......@@ -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
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
)
await new_coordinator.async_config_entry_first_refresh()
entry.runtime_data.append(
WeheatData(
heat_pump_info=new_heat_pump,
data_coordinator=new_data_coordinator,
energy_coordinator=new_energy_coordinator,
)
)
entry.runtime_data.append(new_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__)
......
"""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,70 @@ 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
async def _async_update_data(self) -> HeatPump:
"""Fetch data from the API."""
await self.session.async_ensure_token_valid()
@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
try:
await self._heat_pump_data.async_get_logs(
self.session.token[CONF_ACCESS_TOKEN]
)
except UnauthorizedException as error:
raise ConfigEntryAuthFailed from error
except EXCEPTIONS as error:
raise UpdateFailed(error) from error
@property
def model(self) -> str:
"""Return the model of the heat pump."""
return self.heat_pump_info.model
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_status(
await self._heat_pump_data.async_get_energy(
self.session.token[CONF_ACCESS_TOKEN]
)
except UnauthorizedException as error:
......@@ -92,3 +155,12 @@ class WeheatDataUpdateCoordinator(DataUpdateCoordinator[HeatPump]):
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:
......
......@@ -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
......
......@@ -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
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment