diff --git a/homeassistant/components/nanoleaf/__init__.py b/homeassistant/components/nanoleaf/__init__.py index 79bb2577bb7..3ed82ec2146 100644 --- a/homeassistant/components/nanoleaf/__init__.py +++ b/homeassistant/components/nanoleaf/__init__.py @@ -1,17 +1,31 @@ """The Nanoleaf integration.""" -from aionanoleaf import InvalidToken, Nanoleaf, Unavailable +from __future__ import annotations + +import asyncio +from dataclasses import dataclass + +from aionanoleaf import EffectsEvent, InvalidToken, Nanoleaf, StateEvent, Unavailable from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, CONF_TOKEN from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.dispatcher import async_dispatcher_send from .const import DOMAIN PLATFORMS = ["button", "light"] +@dataclass +class NanoleafEntryData: + """Class for sharing data within the Nanoleaf integration.""" + + device: Nanoleaf + event_listener: asyncio.Task + + async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Nanoleaf from a config entry.""" nanoleaf = Nanoleaf( @@ -24,8 +38,29 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: except InvalidToken as err: raise ConfigEntryAuthFailed from err - hass.data.setdefault(DOMAIN, {})[entry.entry_id] = nanoleaf + async def _callback_update_light_state(event: StateEvent | EffectsEvent) -> None: + """Receive state and effect event.""" + async_dispatcher_send(hass, f"{DOMAIN}_update_light_{nanoleaf.serial_no}") + + event_listener = asyncio.create_task( + nanoleaf.listen_events( + state_callback=_callback_update_light_state, + effects_callback=_callback_update_light_state, + ) + ) + + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = NanoleafEntryData( + nanoleaf, event_listener + ) hass.config_entries.async_setup_platforms(entry, PLATFORMS) return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload a config entry.""" + await hass.config_entries.async_unload_platforms(entry, PLATFORMS) + entry_data: NanoleafEntryData = hass.data[DOMAIN].pop(entry.entry_id) + entry_data.event_listener.cancel() + return True diff --git a/homeassistant/components/nanoleaf/button.py b/homeassistant/components/nanoleaf/button.py index d85e61fc09c..bf7f9fba9f7 100644 --- a/homeassistant/components/nanoleaf/button.py +++ b/homeassistant/components/nanoleaf/button.py @@ -8,6 +8,7 @@ from homeassistant.const import ENTITY_CATEGORY_CONFIG from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback +from . import NanoleafEntryData from .const import DOMAIN from .entity import NanoleafEntity @@ -16,8 +17,8 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up the Nanoleaf button.""" - nanoleaf: Nanoleaf = hass.data[DOMAIN][entry.entry_id] - async_add_entities([NanoleafIdentifyButton(nanoleaf)]) + entry_data: NanoleafEntryData = hass.data[DOMAIN][entry.entry_id] + async_add_entities([NanoleafIdentifyButton(entry_data.device)]) class NanoleafIdentifyButton(NanoleafEntity, ButtonEntity): diff --git a/homeassistant/components/nanoleaf/light.py b/homeassistant/components/nanoleaf/light.py index e6a602b51f3..f4312b704e2 100644 --- a/homeassistant/components/nanoleaf/light.py +++ b/homeassistant/components/nanoleaf/light.py @@ -1,6 +1,7 @@ """Support for Nanoleaf Lights.""" from __future__ import annotations +from datetime import timedelta import logging import math from typing import Any @@ -26,6 +27,7 @@ from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN 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.util.color import ( @@ -33,6 +35,7 @@ from homeassistant.util.color import ( color_temperature_mired_to_kelvin as mired_to_kelvin, ) +from . import NanoleafEntryData from .const import DOMAIN from .entity import NanoleafEntity @@ -49,6 +52,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( _LOGGER = logging.getLogger(__name__) +SCAN_INTERVAL = timedelta(minutes=5) + async def async_setup_platform( hass: HomeAssistant, @@ -70,8 +75,8 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up the Nanoleaf light.""" - nanoleaf: Nanoleaf = hass.data[DOMAIN][entry.entry_id] - async_add_entities([NanoleafLight(nanoleaf)]) + entry_data: NanoleafEntryData = hass.data[DOMAIN][entry.entry_id] + async_add_entities([NanoleafLight(entry_data.device)]) class NanoleafLight(NanoleafEntity, LightEntity): @@ -188,3 +193,21 @@ class NanoleafLight(NanoleafEntity, LightEntity): if not self.available: _LOGGER.info("Fetching %s data recovered", self.name) self._attr_available = True + + async 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, + ) + ) diff --git a/homeassistant/components/nanoleaf/manifest.json b/homeassistant/components/nanoleaf/manifest.json index d2c2d1ff643..3550b56d352 100644 --- a/homeassistant/components/nanoleaf/manifest.json +++ b/homeassistant/components/nanoleaf/manifest.json @@ -25,5 +25,5 @@ } ], "codeowners": ["@milanmeu"], - "iot_class": "local_polling" + "iot_class": "local_push" } \ No newline at end of file