-
-
Notifications
You must be signed in to change notification settings - Fork 31.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add weheat core integration (#123057)
* Add empty weheat integration * Add first sensor to weheat integration * Add weheat entity to provide device information * Fixed automatic selection for a single heat pump * Replaced integration specific package and removed status sensor * Update const.py * Add reauthentication support for weheat integration * Add test cases for the config flow of the weheat integration * Changed API and OATH url to weheat production environment * Add empty weheat integration * Add first sensor to weheat integration * Add weheat entity to provide device information * Fixed automatic selection for a single heat pump * Replaced integration specific package and removed status sensor * Add reauthentication support for weheat integration * Update const.py * Add test cases for the config flow of the weheat integration * Changed API and OATH url to weheat production environment * Resolved merge conflict after adding weheat package * Apply suggestions from code review Co-authored-by: Joost Lekkerkerker <[email protected]> * Added translation keys, more type info and version bump the weheat package * Adding native property value for weheat sensor * Removed reauth, added weheat sensor description and changed discovery of heat pumps * Added unique ID of user to entity * Replaced string by constants, added test case for duplicate unique id * Removed duplicate constant * Added offline scope * Removed re-auth related code * Simplified oath implementation * Cleanup tests for weheat integration * Added oath scope to tests --------- Co-authored-by: kjell-van-straaten <[email protected]> Co-authored-by: Joost Lekkerkerker <[email protected]>
- Loading branch information
1 parent
ff20131
commit dfcfe78
Showing
21 changed files
with
632 additions
and
0 deletions.
There are no files selected for viewing
Validating CODEOWNERS rules …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
"""The Weheat integration.""" | ||
|
||
from __future__ import annotations | ||
|
||
from weheat.abstractions.discovery import HeatPumpDiscovery | ||
|
||
from homeassistant.config_entries import ConfigEntry | ||
from homeassistant.const import CONF_ACCESS_TOKEN, Platform | ||
from homeassistant.core import HomeAssistant | ||
from homeassistant.helpers.config_entry_oauth2_flow import ( | ||
OAuth2Session, | ||
async_get_config_entry_implementation, | ||
) | ||
|
||
from .const import API_URL, LOGGER | ||
from .coordinator import WeheatDataUpdateCoordinator | ||
|
||
PLATFORMS: list[Platform] = [Platform.SENSOR] | ||
|
||
type WeheatConfigEntry = ConfigEntry[list[WeheatDataUpdateCoordinator]] | ||
|
||
|
||
async def async_setup_entry(hass: HomeAssistant, entry: WeheatConfigEntry) -> bool: | ||
"""Set up Weheat from a config entry.""" | ||
implementation = await async_get_config_entry_implementation(hass, entry) | ||
|
||
session = OAuth2Session(hass, entry, implementation) | ||
|
||
token = session.token[CONF_ACCESS_TOKEN] | ||
entry.runtime_data = [] | ||
|
||
# fetch a list of the heat pumps the entry can access | ||
for pump_info in await HeatPumpDiscovery.discover_active(API_URL, token): | ||
LOGGER.debug("Adding %s", pump_info) | ||
# for each pump, add a coordinator | ||
new_coordinator = WeheatDataUpdateCoordinator(hass, session, pump_info) | ||
|
||
await new_coordinator.async_config_entry_first_refresh() | ||
|
||
entry.runtime_data.append(new_coordinator) | ||
|
||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) | ||
|
||
return True | ||
|
||
|
||
async def async_unload_entry(hass: HomeAssistant, entry: WeheatConfigEntry) -> bool: | ||
"""Unload a config entry.""" | ||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
"""API for Weheat bound to Home Assistant OAuth.""" | ||
|
||
from aiohttp import ClientSession | ||
from weheat.abstractions import AbstractAuth | ||
|
||
from homeassistant.const import CONF_ACCESS_TOKEN | ||
from homeassistant.helpers.config_entry_oauth2_flow import OAuth2Session | ||
|
||
from .const import API_URL | ||
|
||
|
||
class AsyncConfigEntryAuth(AbstractAuth): | ||
"""Provide Weheat authentication tied to an OAuth2 based config entry.""" | ||
|
||
def __init__( | ||
self, | ||
websession: ClientSession, | ||
oauth_session: OAuth2Session, | ||
) -> None: | ||
"""Initialize Weheat auth.""" | ||
super().__init__(websession, host=API_URL) | ||
self._oauth_session = oauth_session | ||
|
||
async def async_get_access_token(self) -> str: | ||
"""Return a valid access token.""" | ||
if not self._oauth_session.valid_token: | ||
await self._oauth_session.async_ensure_token_valid() | ||
|
||
return self._oauth_session.token[CONF_ACCESS_TOKEN] |
11 changes: 11 additions & 0 deletions
11
homeassistant/components/weheat/application_credentials.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
"""application_credentials platform the Weheat integration.""" | ||
|
||
from homeassistant.components.application_credentials import AuthorizationServer | ||
from homeassistant.core import HomeAssistant | ||
|
||
from .const import OAUTH2_AUTHORIZE, OAUTH2_TOKEN | ||
|
||
|
||
async def async_get_authorization_server(hass: HomeAssistant) -> AuthorizationServer: | ||
"""Return authorization server.""" | ||
return AuthorizationServer(authorize_url=OAUTH2_AUTHORIZE, token_url=OAUTH2_TOKEN) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
"""Config flow for Weheat.""" | ||
|
||
import logging | ||
|
||
from weheat.abstractions.user import get_user_id_from_token | ||
|
||
from homeassistant.config_entries import ConfigFlowResult | ||
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_TOKEN | ||
from homeassistant.helpers.config_entry_oauth2_flow import AbstractOAuth2FlowHandler | ||
|
||
from .const import API_URL, DOMAIN, ENTRY_TITLE, OAUTH2_SCOPES | ||
|
||
|
||
class OAuth2FlowHandler(AbstractOAuth2FlowHandler, domain=DOMAIN): | ||
"""Config flow to handle Weheat OAuth2 authentication.""" | ||
|
||
DOMAIN = DOMAIN | ||
|
||
@property | ||
def logger(self) -> logging.Logger: | ||
"""Return logger.""" | ||
return logging.getLogger(__name__) | ||
|
||
@property | ||
def extra_authorize_data(self) -> dict[str, str]: | ||
"""Extra data that needs to be appended to the authorize url.""" | ||
return { | ||
"scope": " ".join(OAUTH2_SCOPES), | ||
} | ||
|
||
async def async_oauth_create_entry(self, data: dict) -> ConfigFlowResult: | ||
"""Override the create entry method to change to the step to find the heat pumps.""" | ||
# get the user id and use that as unique id for this entry | ||
user_id = await get_user_id_from_token( | ||
API_URL, data[CONF_TOKEN][CONF_ACCESS_TOKEN] | ||
) | ||
await self.async_set_unique_id(user_id) | ||
self._abort_if_unique_id_configured() | ||
|
||
return self.async_create_entry(title=ENTRY_TITLE, data=data) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
"""Constants for the Weheat integration.""" | ||
|
||
from logging import Logger, getLogger | ||
|
||
DOMAIN = "weheat" | ||
MANUFACTURER = "Weheat" | ||
ENTRY_TITLE = "Weheat cloud" | ||
ERROR_DESCRIPTION = "error_description" | ||
|
||
OAUTH2_AUTHORIZE = ( | ||
"https://auth.weheat.nl/auth/realms/Weheat/protocol/openid-connect/auth/" | ||
) | ||
OAUTH2_TOKEN = ( | ||
"https://auth.weheat.nl/auth/realms/Weheat/protocol/openid-connect/token/" | ||
) | ||
API_URL = "https://api.weheat.nl" | ||
OAUTH2_SCOPES = ["openid", "offline_access"] | ||
|
||
|
||
UPDATE_INTERVAL = 30 | ||
|
||
LOGGER: Logger = getLogger(__package__) | ||
|
||
DISPLAY_PRECISION_WATTS = 0 | ||
DISPLAY_PRECISION_COP = 1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
"""Define a custom coordinator for the Weheat heatpump integration.""" | ||
|
||
from datetime import timedelta | ||
|
||
from weheat.abstractions.discovery import HeatPumpDiscovery | ||
from weheat.abstractions.heat_pump import HeatPump | ||
from weheat.exceptions import ( | ||
ApiException, | ||
BadRequestException, | ||
ForbiddenException, | ||
NotFoundException, | ||
ServiceException, | ||
UnauthorizedException, | ||
) | ||
|
||
from homeassistant.const import CONF_ACCESS_TOKEN | ||
from homeassistant.core import HomeAssistant | ||
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 | ||
|
||
EXCEPTIONS = ( | ||
ServiceException, | ||
NotFoundException, | ||
ForbiddenException, | ||
UnauthorizedException, | ||
BadRequestException, | ||
ApiException, | ||
) | ||
|
||
|
||
class WeheatDataUpdateCoordinator(DataUpdateCoordinator[HeatPump]): | ||
"""A custom coordinator for the Weheat heatpump integration.""" | ||
|
||
def __init__( | ||
self, | ||
hass: HomeAssistant, | ||
session: OAuth2Session, | ||
heat_pump: HeatPumpDiscovery.HeatPumpInfo, | ||
) -> None: | ||
"""Initialize the data coordinator.""" | ||
super().__init__( | ||
hass, | ||
logger=LOGGER, | ||
name=DOMAIN, | ||
update_interval=timedelta(seconds=UPDATE_INTERVAL), | ||
) | ||
self._heat_pump_info = heat_pump | ||
self._heat_pump_data = HeatPump(API_URL, self._heat_pump_info.uuid) | ||
|
||
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 | ||
|
||
def fetch_data(self) -> HeatPump: | ||
"""Get the data from the API.""" | ||
try: | ||
self._heat_pump_data.get_status(self.session.token[CONF_ACCESS_TOKEN]) | ||
except EXCEPTIONS as error: | ||
raise UpdateFailed(error) from error | ||
|
||
return self._heat_pump_data | ||
|
||
async def _async_update_data(self) -> HeatPump: | ||
"""Fetch data from the API.""" | ||
await self.session.async_ensure_token_valid() | ||
|
||
return await self.hass.async_add_executor_job(self.fetch_data) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
"""Base entity for Weheat.""" | ||
|
||
from homeassistant.helpers.device_registry import DeviceInfo | ||
from homeassistant.helpers.update_coordinator import CoordinatorEntity | ||
|
||
from .const import DOMAIN, MANUFACTURER | ||
from .coordinator import WeheatDataUpdateCoordinator | ||
|
||
|
||
class WeheatEntity(CoordinatorEntity[WeheatDataUpdateCoordinator]): | ||
"""Defines a base Weheat entity.""" | ||
|
||
_attr_has_entity_name = True | ||
|
||
def __init__( | ||
self, | ||
coordinator: WeheatDataUpdateCoordinator, | ||
) -> None: | ||
"""Initialize the Weheat entity.""" | ||
super().__init__(coordinator) | ||
|
||
self._attr_device_info = DeviceInfo( | ||
identifiers={(DOMAIN, coordinator.heatpump_id)}, | ||
name=coordinator.readable_name, | ||
manufacturer=MANUFACTURER, | ||
model=coordinator.model, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{ | ||
"entity": { | ||
"sensor": { | ||
"power_output": { | ||
"default": "mdi:heat-wave" | ||
}, | ||
"power_input": { | ||
"default": "mdi:lightning-bolt" | ||
}, | ||
"cop": { | ||
"default": "mdi:speedometer" | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"domain": "weheat", | ||
"name": "Weheat", | ||
"codeowners": ["@jesperraemaekers"], | ||
"config_flow": true, | ||
"dependencies": ["application_credentials"], | ||
"documentation": "https://www.home-assistant.io/integrations/weheat", | ||
"iot_class": "cloud_polling", | ||
"requirements": ["weheat==2024.09.05"] | ||
} |
Oops, something went wrong.