diff --git a/homeassistant/components/elgato/__init__.py b/homeassistant/components/elgato/__init__.py index d90b4a2dccf..805a70613f9 100644 --- a/homeassistant/components/elgato/__init__.py +++ b/homeassistant/components/elgato/__init__.py @@ -1,16 +1,15 @@ """Support for Elgato Lights.""" -import logging from typing import NamedTuple -from elgato import Elgato, ElgatoConnectionError, Info +from elgato import Elgato, Info, State from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, CONF_PORT, Platform from homeassistant.core import HomeAssistant -from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from .const import DOMAIN +from .const import DOMAIN, LOGGER, SCAN_INTERVAL PLATFORMS = [Platform.BUTTON, Platform.LIGHT] @@ -18,6 +17,7 @@ PLATFORMS = [Platform.BUTTON, Platform.LIGHT] class HomeAssistantElgatoData(NamedTuple): """Elgato data stored in the Home Assistant data object.""" + coordinator: DataUpdateCoordinator[State] client: Elgato info: Info @@ -31,15 +31,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: session=session, ) - # Ensure we can connect to it - try: - info = await elgato.info() - except ElgatoConnectionError as exception: - logging.getLogger(__name__).debug("Unable to connect: %s", exception) - raise ConfigEntryNotReady from exception + coordinator: DataUpdateCoordinator[State] = DataUpdateCoordinator( + hass, + LOGGER, + name=f"{DOMAIN}_{entry.data[CONF_HOST]}", + update_interval=SCAN_INTERVAL, + update_method=elgato.state, + ) + await coordinator.async_config_entry_first_refresh() + info = await elgato.info() hass.data.setdefault(DOMAIN, {})[entry.entry_id] = HomeAssistantElgatoData( client=elgato, + coordinator=coordinator, info=info, ) diff --git a/homeassistant/components/elgato/const.py b/homeassistant/components/elgato/const.py index 17a9090841a..e9a93387e63 100644 --- a/homeassistant/components/elgato/const.py +++ b/homeassistant/components/elgato/const.py @@ -1,7 +1,15 @@ """Constants for the Elgato Light integration.""" +from __future__ import annotations + +from datetime import timedelta +import logging +from typing import Final # Integration domain -DOMAIN = "elgato" +DOMAIN: Final = "elgato" + +LOGGER = logging.getLogger(__package__) +SCAN_INTERVAL = timedelta(seconds=10) # Attributes ATTR_ON = "on" diff --git a/homeassistant/components/elgato/light.py b/homeassistant/components/elgato/light.py index 9d901ff0857..cc3dd93d9c4 100644 --- a/homeassistant/components/elgato/light.py +++ b/homeassistant/components/elgato/light.py @@ -1,8 +1,6 @@ """Support for Elgato lights.""" from __future__ import annotations -from datetime import timedelta -import logging from typing import Any from elgato import Elgato, ElgatoError, Info, Settings, State @@ -22,15 +20,16 @@ from homeassistant.helpers.entity_platform import ( AddEntitiesCallback, async_get_current_platform, ) +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + DataUpdateCoordinator, +) from . import HomeAssistantElgatoData -from .const import DOMAIN, SERVICE_IDENTIFY +from .const import DOMAIN, LOGGER, SERVICE_IDENTIFY from .entity import ElgatoEntity -_LOGGER = logging.getLogger(__name__) - PARALLEL_UPDATES = 1 -SCAN_INTERVAL = timedelta(seconds=10) async def async_setup_entry( @@ -42,7 +41,16 @@ async def async_setup_entry( data: HomeAssistantElgatoData = hass.data[DOMAIN][entry.entry_id] settings = await data.client.settings() async_add_entities( - [ElgatoLight(data.client, data.info, entry.data.get(CONF_MAC), settings)], True + [ + ElgatoLight( + data.coordinator, + data.client, + data.info, + entry.data.get(CONF_MAC), + settings, + ) + ], + True, ) platform = async_get_current_platform() @@ -53,15 +61,22 @@ async def async_setup_entry( ) -class ElgatoLight(ElgatoEntity, LightEntity): +class ElgatoLight(ElgatoEntity, CoordinatorEntity, LightEntity): """Defines an Elgato Light.""" + coordinator: DataUpdateCoordinator[State] + def __init__( - self, client: Elgato, info: Info, mac: str | None, settings: Settings + self, + coordinator: DataUpdateCoordinator, + client: Elgato, + info: Info, + mac: str | None, + settings: Settings, ) -> None: """Initialize Elgato Light.""" super().__init__(client, info, mac) - self._state: State | None = None + CoordinatorEntity.__init__(self, coordinator) min_mired = 143 max_mired = 344 @@ -79,27 +94,20 @@ class ElgatoLight(ElgatoEntity, LightEntity): self._attr_supported_color_modes = supported_color_modes self._attr_unique_id = info.serial_number - @property - def available(self) -> bool: - """Return True if entity is available.""" - return self._state is not None - @property def brightness(self) -> int | None: """Return the brightness of this light between 1..255.""" - assert self._state is not None - return round((self._state.brightness * 255) / 100) + return round((self.coordinator.data.brightness * 255) / 100) @property def color_temp(self) -> int | None: """Return the CT color value in mireds.""" - assert self._state is not None - return self._state.temperature + return self.coordinator.data.temperature @property def color_mode(self) -> str | None: """Return the color mode of the light.""" - if self._state and self._state.hue is not None: + if self.coordinator.data.hue is not None: return COLOR_MODE_HS return COLOR_MODE_COLOR_TEMP @@ -107,28 +115,20 @@ class ElgatoLight(ElgatoEntity, LightEntity): @property def hs_color(self) -> tuple[float, float] | None: """Return the hue and saturation color value [float, float].""" - if ( - self._state is None - or self._state.hue is None - or self._state.saturation is None - ): - return None - - return (self._state.hue, self._state.saturation) + return (self.coordinator.data.hue or 0, self.coordinator.data.saturation or 0) @property def is_on(self) -> bool: """Return the state of the light.""" - assert self._state is not None - return self._state.on + return self.coordinator.data.on async def async_turn_off(self, **kwargs: Any) -> None: """Turn off the light.""" try: await self.client.light(on=False) except ElgatoError: - _LOGGER.error("An error occurred while updating the Elgato Light") - self._state = None + LOGGER.error("An error occurred while updating the Elgato Light") + await self.coordinator.async_refresh() async def async_turn_on(self, **kwargs: Any) -> None: """Turn on the light.""" @@ -165,25 +165,13 @@ class ElgatoLight(ElgatoEntity, LightEntity): temperature=temperature, ) except ElgatoError: - _LOGGER.error("An error occurred while updating the Elgato Light") - self._state = None - - async def async_update(self) -> None: - """Update Elgato entity.""" - restoring = self._state is None - try: - self._state = await self.client.state() - if restoring: - _LOGGER.info("Connection restored") - except ElgatoError as err: - meth = _LOGGER.error if self._state else _LOGGER.debug - meth("An error occurred while updating the Elgato Light: %s", err) - self._state = None + LOGGER.error("An error occurred while updating the Elgato Light") + await self.coordinator.async_refresh() async def async_identify(self) -> None: """Identify the light, will make it blink.""" try: await self.client.identify() except ElgatoError: - _LOGGER.exception("An error occurred while identifying the Elgato Light") - self._state = None + LOGGER.exception("An error occurred while identifying the Elgato Light") + await self.coordinator.async_refresh() diff --git a/tests/components/elgato/test_init.py b/tests/components/elgato/test_init.py index d2633ddf36c..1b566ef8ab2 100644 --- a/tests/components/elgato/test_init.py +++ b/tests/components/elgato/test_init.py @@ -36,11 +36,11 @@ async def test_config_entry_not_ready( mock_elgato: MagicMock, ) -> None: """Test the Elgato configuration entry not ready.""" - mock_elgato.info.side_effect = ElgatoConnectionError + mock_elgato.state.side_effect = ElgatoConnectionError mock_config_entry.add_to_hass(hass) await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() - assert len(mock_elgato.info.mock_calls) == 1 + assert len(mock_elgato.state.mock_calls) == 1 assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY