diff --git a/.coveragerc b/.coveragerc index fc6f82547c5..16a22b1323c 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1020,6 +1020,7 @@ omit = homeassistant/components/permobil/entity.py homeassistant/components/permobil/sensor.py homeassistant/components/philips_js/__init__.py + homeassistant/components/philips_js/coordinator.py homeassistant/components/philips_js/light.py homeassistant/components/philips_js/media_player.py homeassistant/components/philips_js/remote.py diff --git a/homeassistant/components/philips_js/__init__.py b/homeassistant/components/philips_js/__init__.py index ee7059d25bf..93f869e849d 100644 --- a/homeassistant/components/philips_js/__init__.py +++ b/homeassistant/components/philips_js/__init__.py @@ -2,18 +2,9 @@ from __future__ import annotations -import asyncio -from collections.abc import Mapping -from datetime import timedelta import logging -from typing import Any -from haphilipsjs import ( - AutenticationFailure, - ConnectionFailure, - GeneralFailure, - PhilipsTV, -) +from haphilipsjs import PhilipsTV from haphilipsjs.typing import SystemType from homeassistant.config_entries import ConfigEntry @@ -24,13 +15,10 @@ from homeassistant.const import ( CONF_USERNAME, Platform, ) -from homeassistant.core import HomeAssistant, callback -from homeassistant.exceptions import ConfigEntryAuthFailed -from homeassistant.helpers.debounce import Debouncer -from homeassistant.helpers.device_registry import DeviceInfo -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed +from homeassistant.core import HomeAssistant -from .const import CONF_ALLOW_NOTIFY, CONF_SYSTEM, DOMAIN +from .const import CONF_SYSTEM +from .coordinator import PhilipsTVDataUpdateCoordinator PLATFORMS = [ Platform.BINARY_SENSOR, @@ -42,7 +30,7 @@ PLATFORMS = [ LOGGER = logging.getLogger(__name__) -PhilipsTVConfigEntry = ConfigEntry["PhilipsTVDataUpdateCoordinator"] +PhilipsTVConfigEntry = ConfigEntry[PhilipsTVDataUpdateCoordinator] async def async_setup_entry(hass: HomeAssistant, entry: PhilipsTVConfigEntry) -> bool: @@ -81,115 +69,3 @@ async def async_update_entry(hass: HomeAssistant, entry: PhilipsTVConfigEntry) - async def async_unload_entry(hass: HomeAssistant, entry: PhilipsTVConfigEntry) -> bool: """Unload a config entry.""" return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) - - -class PhilipsTVDataUpdateCoordinator(DataUpdateCoordinator[None]): # pylint: disable=hass-enforce-coordinator-module - """Coordinator to update data.""" - - config_entry: ConfigEntry - - def __init__( - self, hass: HomeAssistant, api: PhilipsTV, options: Mapping[str, Any] - ) -> None: - """Set up the coordinator.""" - self.api = api - self.options = options - self._notify_future: asyncio.Task | None = None - - super().__init__( - hass, - LOGGER, - name=DOMAIN, - update_interval=timedelta(seconds=30), - request_refresh_debouncer=Debouncer( - hass, LOGGER, cooldown=2.0, immediate=False - ), - ) - - @property - def device_info(self) -> DeviceInfo: - """Return device info.""" - return DeviceInfo( - identifiers={ - (DOMAIN, self.unique_id), - }, - manufacturer="Philips", - model=self.system.get("model"), - name=self.system["name"], - sw_version=self.system.get("softwareversion"), - ) - - @property - def system(self) -> SystemType: - """Return the system descriptor.""" - if self.api.system: - return self.api.system - return self.config_entry.data[CONF_SYSTEM] - - @property - def unique_id(self) -> str: - """Return the system descriptor.""" - entry = self.config_entry - if entry.unique_id: - return entry.unique_id - assert entry.entry_id - return entry.entry_id - - @property - def _notify_wanted(self): - """Return if the notify feature should be active. - - We only run it when TV is considered fully on. When powerstate is in standby, the TV - will go in low power states and seemingly break the http server in odd ways. - """ - return ( - self.api.on - and self.api.powerstate == "On" - and self.api.notify_change_supported - and self.options.get(CONF_ALLOW_NOTIFY, False) - ) - - async def _notify_task(self): - while self._notify_wanted: - try: - res = await self.api.notifyChange(130) - except (ConnectionFailure, AutenticationFailure): - res = None - - if res: - self.async_set_updated_data(None) - elif res is None: - LOGGER.debug("Aborting notify due to unexpected return") - break - - @callback - def _async_notify_stop(self): - if self._notify_future: - self._notify_future.cancel() - self._notify_future = None - - @callback - def _async_notify_schedule(self): - if self._notify_future and not self._notify_future.done(): - return - - if self._notify_wanted: - self._notify_future = asyncio.create_task(self._notify_task()) - - @callback - def _unschedule_refresh(self) -> None: - """Remove data update.""" - super()._unschedule_refresh() - self._async_notify_stop() - - async def _async_update_data(self): - """Fetch the latest data from the source.""" - try: - await self.api.update() - self._async_notify_schedule() - except ConnectionFailure: - pass - except AutenticationFailure as exception: - raise ConfigEntryAuthFailed(str(exception)) from exception - except GeneralFailure as exception: - raise UpdateFailed(str(exception)) from exception diff --git a/homeassistant/components/philips_js/binary_sensor.py b/homeassistant/components/philips_js/binary_sensor.py index 5e8c10ec06a..6de814efd97 100644 --- a/homeassistant/components/philips_js/binary_sensor.py +++ b/homeassistant/components/philips_js/binary_sensor.py @@ -13,7 +13,8 @@ from homeassistant.components.binary_sensor import ( from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import PhilipsTVConfigEntry, PhilipsTVDataUpdateCoordinator +from . import PhilipsTVConfigEntry +from .coordinator import PhilipsTVDataUpdateCoordinator from .entity import PhilipsJsEntity diff --git a/homeassistant/components/philips_js/coordinator.py b/homeassistant/components/philips_js/coordinator.py new file mode 100644 index 00000000000..cae59fa5123 --- /dev/null +++ b/homeassistant/components/philips_js/coordinator.py @@ -0,0 +1,140 @@ +"""Coordinator for the Philips TV integration.""" + +from __future__ import annotations + +import asyncio +from collections.abc import Mapping +from datetime import timedelta +import logging +from typing import Any + +from haphilipsjs import ( + AutenticationFailure, + ConnectionFailure, + GeneralFailure, + PhilipsTV, +) +from haphilipsjs.typing import SystemType + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import ConfigEntryAuthFailed +from homeassistant.helpers.debounce import Debouncer +from homeassistant.helpers.device_registry import DeviceInfo +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed + +from .const import CONF_ALLOW_NOTIFY, CONF_SYSTEM, DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +class PhilipsTVDataUpdateCoordinator(DataUpdateCoordinator[None]): + """Coordinator to update data.""" + + config_entry: ConfigEntry + + def __init__( + self, hass: HomeAssistant, api: PhilipsTV, options: Mapping[str, Any] + ) -> None: + """Set up the coordinator.""" + self.api = api + self.options = options + self._notify_future: asyncio.Task | None = None + + super().__init__( + hass, + _LOGGER, + name=DOMAIN, + update_interval=timedelta(seconds=30), + request_refresh_debouncer=Debouncer( + hass, _LOGGER, cooldown=2.0, immediate=False + ), + ) + + @property + def device_info(self) -> DeviceInfo: + """Return device info.""" + return DeviceInfo( + identifiers={ + (DOMAIN, self.unique_id), + }, + manufacturer="Philips", + model=self.system.get("model"), + name=self.system["name"], + sw_version=self.system.get("softwareversion"), + ) + + @property + def system(self) -> SystemType: + """Return the system descriptor.""" + if self.api.system: + return self.api.system + return self.config_entry.data[CONF_SYSTEM] + + @property + def unique_id(self) -> str: + """Return the system descriptor.""" + entry = self.config_entry + if entry.unique_id: + return entry.unique_id + assert entry.entry_id + return entry.entry_id + + @property + def _notify_wanted(self): + """Return if the notify feature should be active. + + We only run it when TV is considered fully on. When powerstate is in standby, the TV + will go in low power states and seemingly break the http server in odd ways. + """ + return ( + self.api.on + and self.api.powerstate == "On" + and self.api.notify_change_supported + and self.options.get(CONF_ALLOW_NOTIFY, False) + ) + + async def _notify_task(self): + while self._notify_wanted: + try: + res = await self.api.notifyChange(130) + except (ConnectionFailure, AutenticationFailure): + res = None + + if res: + self.async_set_updated_data(None) + elif res is None: + _LOGGER.debug("Aborting notify due to unexpected return") + break + + @callback + def _async_notify_stop(self): + if self._notify_future: + self._notify_future.cancel() + self._notify_future = None + + @callback + def _async_notify_schedule(self): + if self._notify_future and not self._notify_future.done(): + return + + if self._notify_wanted: + self._notify_future = asyncio.create_task(self._notify_task()) + + @callback + def _unschedule_refresh(self) -> None: + """Remove data update.""" + super()._unschedule_refresh() + self._async_notify_stop() + + async def _async_update_data(self): + """Fetch the latest data from the source.""" + try: + await self.api.update() + self._async_notify_schedule() + except ConnectionFailure: + pass + except AutenticationFailure as exception: + raise ConfigEntryAuthFailed(str(exception)) from exception + except GeneralFailure as exception: + raise UpdateFailed(str(exception)) from exception diff --git a/homeassistant/components/philips_js/entity.py b/homeassistant/components/philips_js/entity.py index e0d97f940d0..8d8090318f9 100644 --- a/homeassistant/components/philips_js/entity.py +++ b/homeassistant/components/philips_js/entity.py @@ -4,7 +4,7 @@ from __future__ import annotations from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import PhilipsTVDataUpdateCoordinator +from .coordinator import PhilipsTVDataUpdateCoordinator class PhilipsJsEntity(CoordinatorEntity[PhilipsTVDataUpdateCoordinator]): diff --git a/homeassistant/components/philips_js/light.py b/homeassistant/components/philips_js/light.py index 27b0522debb..d08ecdba8a6 100644 --- a/homeassistant/components/philips_js/light.py +++ b/homeassistant/components/philips_js/light.py @@ -21,7 +21,8 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.util.color import color_hsv_to_RGB, color_RGB_to_hsv -from . import PhilipsTVConfigEntry, PhilipsTVDataUpdateCoordinator +from . import PhilipsTVConfigEntry +from .coordinator import PhilipsTVDataUpdateCoordinator from .entity import PhilipsJsEntity EFFECT_PARTITION = ": " diff --git a/homeassistant/components/philips_js/media_player.py b/homeassistant/components/philips_js/media_player.py index ab71f8bb727..bd8727ae9c1 100644 --- a/homeassistant/components/philips_js/media_player.py +++ b/homeassistant/components/philips_js/media_player.py @@ -21,7 +21,8 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.trigger import PluggableAction -from . import LOGGER as _LOGGER, PhilipsTVConfigEntry, PhilipsTVDataUpdateCoordinator +from . import LOGGER as _LOGGER, PhilipsTVConfigEntry +from .coordinator import PhilipsTVDataUpdateCoordinator from .entity import PhilipsJsEntity from .helpers import async_get_turn_on_trigger diff --git a/homeassistant/components/philips_js/remote.py b/homeassistant/components/philips_js/remote.py index ed63c7ce68d..f8d9cb0885d 100644 --- a/homeassistant/components/philips_js/remote.py +++ b/homeassistant/components/philips_js/remote.py @@ -16,7 +16,8 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.trigger import PluggableAction -from . import LOGGER, PhilipsTVConfigEntry, PhilipsTVDataUpdateCoordinator +from . import LOGGER, PhilipsTVConfigEntry +from .coordinator import PhilipsTVDataUpdateCoordinator from .entity import PhilipsJsEntity from .helpers import async_get_turn_on_trigger diff --git a/homeassistant/components/philips_js/switch.py b/homeassistant/components/philips_js/switch.py index 93c4af24d98..b35b2ad4ff1 100644 --- a/homeassistant/components/philips_js/switch.py +++ b/homeassistant/components/philips_js/switch.py @@ -8,7 +8,8 @@ from homeassistant.components.switch import SwitchEntity from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import PhilipsTVConfigEntry, PhilipsTVDataUpdateCoordinator +from . import PhilipsTVConfigEntry +from .coordinator import PhilipsTVDataUpdateCoordinator from .entity import PhilipsJsEntity HUE_POWER_OFF = "Off"