Move lyric coordinator to separate module (#147357)

This commit is contained in:
epenet 2025-06-23 15:10:12 +02:00 committed by GitHub
parent 87ecf552dc
commit 7ec2e0c524
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 105 additions and 74 deletions

View File

@ -2,25 +2,16 @@
from __future__ import annotations from __future__ import annotations
import asyncio
from datetime import timedelta
from http import HTTPStatus
import logging
from aiohttp.client_exceptions import ClientResponseError
from aiolyric import Lyric from aiolyric import Lyric
from aiolyric.exceptions import LyricAuthenticationException, LyricException
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform from homeassistant.const import Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers import ( from homeassistant.helpers import (
aiohttp_client, aiohttp_client,
config_entry_oauth2_flow, config_entry_oauth2_flow,
config_validation as cv, config_validation as cv,
) )
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .api import ( from .api import (
ConfigEntryLyricClient, ConfigEntryLyricClient,
@ -28,11 +19,10 @@ from .api import (
OAuth2SessionLyric, OAuth2SessionLyric,
) )
from .const import DOMAIN from .const import DOMAIN
from .coordinator import LyricDataUpdateCoordinator
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN) CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
_LOGGER = logging.getLogger(__name__)
PLATFORMS = [Platform.CLIMATE, Platform.SENSOR] PLATFORMS = [Platform.CLIMATE, Platform.SENSOR]
@ -54,53 +44,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
client_id = implementation.client_id client_id = implementation.client_id
lyric = Lyric(client, client_id) lyric = Lyric(client, client_id)
async def async_update_data(force_refresh_token: bool = False) -> Lyric: coordinator = LyricDataUpdateCoordinator(
"""Fetch data from Lyric."""
try:
if not force_refresh_token:
await oauth_session.async_ensure_token_valid()
else:
await oauth_session.force_refresh_token()
except ClientResponseError as exception:
if exception.status in (HTTPStatus.UNAUTHORIZED, HTTPStatus.FORBIDDEN):
raise ConfigEntryAuthFailed from exception
raise UpdateFailed(exception) from exception
try:
async with asyncio.timeout(60):
await lyric.get_locations()
await asyncio.gather(
*(
lyric.get_thermostat_rooms(
location.location_id, device.device_id
)
for location in lyric.locations
for device in location.devices
if device.device_class == "Thermostat"
and device.device_id.startswith("LCC")
)
)
except LyricAuthenticationException as exception:
# Attempt to refresh the token before failing.
# Honeywell appear to have issues keeping tokens saved.
_LOGGER.debug("Authentication failed. Attempting to refresh token")
if not force_refresh_token:
return await async_update_data(force_refresh_token=True)
raise ConfigEntryAuthFailed from exception
except (LyricException, ClientResponseError) as exception:
raise UpdateFailed(exception) from exception
return lyric
coordinator = DataUpdateCoordinator[Lyric](
hass, hass,
_LOGGER,
config_entry=entry, config_entry=entry,
# Name of the data. For logging purposes. oauth_session=oauth_session,
name="lyric_coordinator", lyric=lyric,
update_method=async_update_data,
# Polling interval. Will only be polled if there are subscribers.
update_interval=timedelta(seconds=300),
) )
# Fetch initial data so we have data when entities subscribe # Fetch initial data so we have data when entities subscribe

View File

@ -8,7 +8,6 @@ import logging
from time import localtime, strftime, time from time import localtime, strftime, time
from typing import Any from typing import Any
from aiolyric import Lyric
from aiolyric.objects.device import LyricDevice from aiolyric.objects.device import LyricDevice
from aiolyric.objects.location import LyricLocation from aiolyric.objects.location import LyricLocation
import voluptuous as vol import voluptuous as vol
@ -37,7 +36,6 @@ from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv, entity_platform from homeassistant.helpers import config_validation as cv, entity_platform
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.typing import VolDictType from homeassistant.helpers.typing import VolDictType
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import ( from .const import (
DOMAIN, DOMAIN,
@ -48,6 +46,7 @@ from .const import (
PRESET_TEMPORARY_HOLD, PRESET_TEMPORARY_HOLD,
PRESET_VACATION_HOLD, PRESET_VACATION_HOLD,
) )
from .coordinator import LyricDataUpdateCoordinator
from .entity import LyricDeviceEntity from .entity import LyricDeviceEntity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -126,7 +125,7 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback, async_add_entities: AddConfigEntryEntitiesCallback,
) -> None: ) -> None:
"""Set up the Honeywell Lyric climate platform based on a config entry.""" """Set up the Honeywell Lyric climate platform based on a config entry."""
coordinator: DataUpdateCoordinator[Lyric] = hass.data[DOMAIN][entry.entry_id] coordinator: LyricDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
async_add_entities( async_add_entities(
( (
@ -164,7 +163,7 @@ class LyricThermostatType(enum.Enum):
class LyricClimate(LyricDeviceEntity, ClimateEntity): class LyricClimate(LyricDeviceEntity, ClimateEntity):
"""Defines a Honeywell Lyric climate entity.""" """Defines a Honeywell Lyric climate entity."""
coordinator: DataUpdateCoordinator[Lyric] coordinator: LyricDataUpdateCoordinator
entity_description: ClimateEntityDescription entity_description: ClimateEntityDescription
_attr_name = None _attr_name = None
@ -178,7 +177,7 @@ class LyricClimate(LyricDeviceEntity, ClimateEntity):
def __init__( def __init__(
self, self,
coordinator: DataUpdateCoordinator[Lyric], coordinator: LyricDataUpdateCoordinator,
description: ClimateEntityDescription, description: ClimateEntityDescription,
location: LyricLocation, location: LyricLocation,
device: LyricDevice, device: LyricDevice,

View File

@ -0,0 +1,87 @@
"""The Honeywell Lyric integration."""
from __future__ import annotations
import asyncio
from datetime import timedelta
from http import HTTPStatus
import logging
from aiohttp.client_exceptions import ClientResponseError
from aiolyric import Lyric
from aiolyric.exceptions import LyricAuthenticationException, LyricException
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .api import OAuth2SessionLyric
_LOGGER = logging.getLogger(__name__)
class LyricDataUpdateCoordinator(DataUpdateCoordinator[Lyric]):
"""Data update coordinator for Honeywell Lyric."""
config_entry: ConfigEntry
def __init__(
self,
hass: HomeAssistant,
config_entry: ConfigEntry,
oauth_session: OAuth2SessionLyric,
lyric: Lyric,
) -> None:
"""Initialize the coordinator."""
super().__init__(
hass,
_LOGGER,
config_entry=config_entry,
name="lyric_coordinator",
update_interval=timedelta(seconds=300),
)
self.oauth_session = oauth_session
self.lyric = lyric
async def _async_update_data(self) -> Lyric:
"""Fetch data from Lyric."""
return await self._run_update(False)
async def _run_update(self, force_refresh_token: bool) -> Lyric:
"""Fetch data from Lyric."""
try:
if not force_refresh_token:
await self.oauth_session.async_ensure_token_valid()
else:
await self.oauth_session.force_refresh_token()
except ClientResponseError as exception:
if exception.status in (HTTPStatus.UNAUTHORIZED, HTTPStatus.FORBIDDEN):
raise ConfigEntryAuthFailed from exception
raise UpdateFailed(exception) from exception
try:
async with asyncio.timeout(60):
await self.lyric.get_locations()
await asyncio.gather(
*(
self.lyric.get_thermostat_rooms(
location.location_id, device.device_id
)
for location in self.lyric.locations
for device in location.devices
if device.device_class == "Thermostat"
and device.device_id.startswith("LCC")
)
)
except LyricAuthenticationException as exception:
# Attempt to refresh the token before failing.
# Honeywell appear to have issues keeping tokens saved.
_LOGGER.debug("Authentication failed. Attempting to refresh token")
if not force_refresh_token:
return await self._run_update(True)
raise ConfigEntryAuthFailed from exception
except (LyricException, ClientResponseError) as exception:
raise UpdateFailed(exception) from exception
return self.lyric

View File

@ -2,27 +2,25 @@
from __future__ import annotations from __future__ import annotations
from aiolyric import Lyric
from aiolyric.objects.device import LyricDevice from aiolyric.objects.device import LyricDevice
from aiolyric.objects.location import LyricLocation from aiolyric.objects.location import LyricLocation
from aiolyric.objects.priority import LyricAccessory, LyricRoom from aiolyric.objects.priority import LyricAccessory, LyricRoom
from homeassistant.helpers import device_registry as dr from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.update_coordinator import ( from homeassistant.helpers.update_coordinator import CoordinatorEntity
CoordinatorEntity,
DataUpdateCoordinator, from .coordinator import LyricDataUpdateCoordinator
)
class LyricEntity(CoordinatorEntity[DataUpdateCoordinator[Lyric]]): class LyricEntity(CoordinatorEntity[LyricDataUpdateCoordinator]):
"""Defines a base Honeywell Lyric entity.""" """Defines a base Honeywell Lyric entity."""
_attr_has_entity_name = True _attr_has_entity_name = True
def __init__( def __init__(
self, self,
coordinator: DataUpdateCoordinator[Lyric], coordinator: LyricDataUpdateCoordinator,
location: LyricLocation, location: LyricLocation,
device: LyricDevice, device: LyricDevice,
key: str, key: str,
@ -71,7 +69,7 @@ class LyricAccessoryEntity(LyricDeviceEntity):
def __init__( def __init__(
self, self,
coordinator: DataUpdateCoordinator[Lyric], coordinator: LyricDataUpdateCoordinator,
location: LyricLocation, location: LyricLocation,
device: LyricDevice, device: LyricDevice,
room: LyricRoom, room: LyricRoom,

View File

@ -6,7 +6,6 @@ from collections.abc import Callable
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime, timedelta from datetime import datetime, timedelta
from aiolyric import Lyric
from aiolyric.objects.device import LyricDevice from aiolyric.objects.device import LyricDevice
from aiolyric.objects.location import LyricLocation from aiolyric.objects.location import LyricLocation
from aiolyric.objects.priority import LyricAccessory, LyricRoom from aiolyric.objects.priority import LyricAccessory, LyricRoom
@ -22,7 +21,6 @@ from homeassistant.const import PERCENTAGE, UnitOfTemperature
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.typing import StateType from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from homeassistant.util import dt as dt_util from homeassistant.util import dt as dt_util
from .const import ( from .const import (
@ -33,6 +31,7 @@ from .const import (
PRESET_TEMPORARY_HOLD, PRESET_TEMPORARY_HOLD,
PRESET_VACATION_HOLD, PRESET_VACATION_HOLD,
) )
from .coordinator import LyricDataUpdateCoordinator
from .entity import LyricAccessoryEntity, LyricDeviceEntity from .entity import LyricAccessoryEntity, LyricDeviceEntity
LYRIC_SETPOINT_STATUS_NAMES = { LYRIC_SETPOINT_STATUS_NAMES = {
@ -164,7 +163,7 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback, async_add_entities: AddConfigEntryEntitiesCallback,
) -> None: ) -> None:
"""Set up the Honeywell Lyric sensor platform based on a config entry.""" """Set up the Honeywell Lyric sensor platform based on a config entry."""
coordinator: DataUpdateCoordinator[Lyric] = hass.data[DOMAIN][entry.entry_id] coordinator: LyricDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
async_add_entities( async_add_entities(
LyricSensor( LyricSensor(
@ -199,7 +198,7 @@ class LyricSensor(LyricDeviceEntity, SensorEntity):
def __init__( def __init__(
self, self,
coordinator: DataUpdateCoordinator[Lyric], coordinator: LyricDataUpdateCoordinator,
description: LyricSensorEntityDescription, description: LyricSensorEntityDescription,
location: LyricLocation, location: LyricLocation,
device: LyricDevice, device: LyricDevice,
@ -231,7 +230,7 @@ class LyricAccessorySensor(LyricAccessoryEntity, SensorEntity):
def __init__( def __init__(
self, self,
coordinator: DataUpdateCoordinator[Lyric], coordinator: LyricDataUpdateCoordinator,
description: LyricSensorAccessoryEntityDescription, description: LyricSensorAccessoryEntityDescription,
location: LyricLocation, location: LyricLocation,
parentDevice: LyricDevice, parentDevice: LyricDevice,