From b1dcf7e0d8a7babcc691b326daf7d4b2d55c446a Mon Sep 17 00:00:00 2001 From: Milan Meulemans Date: Sun, 6 Feb 2022 23:11:52 +0100 Subject: [PATCH] Add DataUpdateCoordinator to Nanoleaf (#65950) --- homeassistant/components/nanoleaf/__init__.py | 42 ++++++++++++----- homeassistant/components/nanoleaf/button.py | 9 ++-- homeassistant/components/nanoleaf/entity.py | 11 +++-- homeassistant/components/nanoleaf/light.py | 47 +++---------------- 4 files changed, 49 insertions(+), 60 deletions(-) diff --git a/homeassistant/components/nanoleaf/__init__.py b/homeassistant/components/nanoleaf/__init__.py index 4560f340416..677c0d4cc2a 100644 --- a/homeassistant/components/nanoleaf/__init__.py +++ b/homeassistant/components/nanoleaf/__init__.py @@ -3,15 +3,17 @@ from __future__ import annotations import asyncio from dataclasses import dataclass +from datetime import timedelta +import logging from aionanoleaf import EffectsEvent, InvalidToken, Nanoleaf, StateEvent, Unavailable from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, CONF_TOKEN, Platform from homeassistant.core import HomeAssistant -from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady +from homeassistant.exceptions import ConfigEntryAuthFailed from homeassistant.helpers.aiohttp_client import async_get_clientsession -from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import DOMAIN @@ -23,6 +25,7 @@ class NanoleafEntryData: """Class for sharing data within the Nanoleaf integration.""" device: Nanoleaf + coordinator: DataUpdateCoordinator event_listener: asyncio.Task @@ -31,26 +34,39 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: nanoleaf = Nanoleaf( async_get_clientsession(hass), entry.data[CONF_HOST], entry.data[CONF_TOKEN] ) - try: - await nanoleaf.get_info() - except Unavailable as err: - raise ConfigEntryNotReady from err - except InvalidToken as err: - raise ConfigEntryAuthFailed from err - async def _callback_update_light_state(event: StateEvent | EffectsEvent) -> None: + async def async_get_state() -> None: + """Get the state of the device.""" + try: + await nanoleaf.get_info() + except Unavailable as err: + raise UpdateFailed from err + except InvalidToken as err: + raise ConfigEntryAuthFailed from err + + coordinator = DataUpdateCoordinator( + hass, + logging.getLogger(__name__), + name=entry.title, + update_interval=timedelta(minutes=1), + update_method=async_get_state, + ) + + await coordinator.async_config_entry_first_refresh() + + async def update_light_state_callback(event: StateEvent | EffectsEvent) -> None: """Receive state and effect event.""" - async_dispatcher_send(hass, f"{DOMAIN}_update_light_{nanoleaf.serial_no}") + coordinator.async_set_updated_data(None) event_listener = asyncio.create_task( nanoleaf.listen_events( - state_callback=_callback_update_light_state, - effects_callback=_callback_update_light_state, + state_callback=update_light_state_callback, + effects_callback=update_light_state_callback, ) ) hass.data.setdefault(DOMAIN, {})[entry.entry_id] = NanoleafEntryData( - nanoleaf, event_listener + nanoleaf, coordinator, event_listener ) hass.config_entries.async_setup_platforms(entry, PLATFORMS) diff --git a/homeassistant/components/nanoleaf/button.py b/homeassistant/components/nanoleaf/button.py index 2d9388196c1..5297e98c88e 100644 --- a/homeassistant/components/nanoleaf/button.py +++ b/homeassistant/components/nanoleaf/button.py @@ -7,6 +7,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from . import NanoleafEntryData from .const import DOMAIN @@ -18,15 +19,17 @@ async def async_setup_entry( ) -> None: """Set up the Nanoleaf button.""" entry_data: NanoleafEntryData = hass.data[DOMAIN][entry.entry_id] - async_add_entities([NanoleafIdentifyButton(entry_data.device)]) + async_add_entities( + [NanoleafIdentifyButton(entry_data.device, entry_data.coordinator)] + ) class NanoleafIdentifyButton(NanoleafEntity, ButtonEntity): """Representation of a Nanoleaf identify button.""" - def __init__(self, nanoleaf: Nanoleaf) -> None: + def __init__(self, nanoleaf: Nanoleaf, coordinator: DataUpdateCoordinator) -> None: """Initialize the Nanoleaf button.""" - super().__init__(nanoleaf) + super().__init__(nanoleaf, coordinator) self._attr_unique_id = f"{nanoleaf.serial_no}_identify" self._attr_name = f"Identify {nanoleaf.name}" self._attr_icon = "mdi:magnify" diff --git a/homeassistant/components/nanoleaf/entity.py b/homeassistant/components/nanoleaf/entity.py index c6efed91787..8df9e243d71 100644 --- a/homeassistant/components/nanoleaf/entity.py +++ b/homeassistant/components/nanoleaf/entity.py @@ -2,16 +2,21 @@ from aionanoleaf import Nanoleaf -from homeassistant.helpers.entity import DeviceInfo, Entity +from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + DataUpdateCoordinator, +) from .const import DOMAIN -class NanoleafEntity(Entity): +class NanoleafEntity(CoordinatorEntity): """Representation of a Nanoleaf entity.""" - def __init__(self, nanoleaf: Nanoleaf) -> None: + def __init__(self, nanoleaf: Nanoleaf, coordinator: DataUpdateCoordinator) -> None: """Initialize an Nanoleaf entity.""" + super().__init__(coordinator) self._nanoleaf = nanoleaf self._attr_device_info = DeviceInfo( identifiers={(DOMAIN, nanoleaf.serial_no)}, diff --git a/homeassistant/components/nanoleaf/light.py b/homeassistant/components/nanoleaf/light.py index b4eb89ac8c7..29c7cb786e6 100644 --- a/homeassistant/components/nanoleaf/light.py +++ b/homeassistant/components/nanoleaf/light.py @@ -1,12 +1,11 @@ """Support for Nanoleaf Lights.""" from __future__ import annotations -from datetime import timedelta import logging import math from typing import Any -from aionanoleaf import Nanoleaf, Unavailable +from aionanoleaf import Nanoleaf import voluptuous as vol from homeassistant.components.light import ( @@ -25,11 +24,11 @@ from homeassistant.components.light import ( ) from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from homeassistant.util.color import ( color_temperature_kelvin_to_mired as kelvin_to_mired, color_temperature_mired_to_kelvin as mired_to_kelvin, @@ -52,8 +51,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( _LOGGER = logging.getLogger(__name__) -SCAN_INTERVAL = timedelta(minutes=5) - async def async_setup_platform( hass: HomeAssistant, @@ -82,15 +79,15 @@ async def async_setup_entry( ) -> None: """Set up the Nanoleaf light.""" entry_data: NanoleafEntryData = hass.data[DOMAIN][entry.entry_id] - async_add_entities([NanoleafLight(entry_data.device)]) + async_add_entities([NanoleafLight(entry_data.device, entry_data.coordinator)]) class NanoleafLight(NanoleafEntity, LightEntity): """Representation of a Nanoleaf Light.""" - def __init__(self, nanoleaf: Nanoleaf) -> None: + def __init__(self, nanoleaf: Nanoleaf, coordinator: DataUpdateCoordinator) -> None: """Initialize the Nanoleaf light.""" - super().__init__(nanoleaf) + super().__init__(nanoleaf, coordinator) self._attr_unique_id = nanoleaf.serial_no self._attr_name = nanoleaf.name self._attr_min_mireds = math.ceil(1000000 / nanoleaf.color_temperature_max) @@ -186,35 +183,3 @@ class NanoleafLight(NanoleafEntity, LightEntity): """Instruct the light to turn off.""" transition: float | None = kwargs.get(ATTR_TRANSITION) await self._nanoleaf.turn_off(None if transition is None else int(transition)) - - async def async_update(self) -> None: - """Fetch new state data for this light.""" - try: - await self._nanoleaf.get_info() - except Unavailable: - if self.available: - _LOGGER.warning("Could not connect to %s", self.name) - self._attr_available = False - return - if not self.available: - _LOGGER.info("Fetching %s data recovered", self.name) - self._attr_available = True - - @callback - def async_handle_update(self) -> None: - """Handle state update.""" - self.async_write_ha_state() - if not self.available: - _LOGGER.info("Connection to %s recovered", self.name) - self._attr_available = True - - async def async_added_to_hass(self) -> None: - """Handle entity being added to Home Assistant.""" - await super().async_added_to_hass() - self.async_on_remove( - async_dispatcher_connect( - self.hass, - f"{DOMAIN}_update_light_{self._nanoleaf.serial_no}", - self.async_handle_update, - ) - )