diff --git a/homeassistant/components/nanoleaf/__init__.py b/homeassistant/components/nanoleaf/__init__.py index 313af5b0ae3..c706f52035f 100644 --- a/homeassistant/components/nanoleaf/__init__.py +++ b/homeassistant/components/nanoleaf/__init__.py @@ -7,7 +7,7 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession -from .const import DEVICE, DOMAIN, NAME, SERIAL_NO +from .const import DOMAIN async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: @@ -22,11 +22,7 @@ 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] = { - DEVICE: nanoleaf, - NAME: nanoleaf.name, - SERIAL_NO: nanoleaf.serial_no, - } + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = nanoleaf hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, "light") diff --git a/homeassistant/components/nanoleaf/const.py b/homeassistant/components/nanoleaf/const.py index 6d393fa3428..505af8ce69d 100644 --- a/homeassistant/components/nanoleaf/const.py +++ b/homeassistant/components/nanoleaf/const.py @@ -1,7 +1,3 @@ """Constants for Nanoleaf integration.""" DOMAIN = "nanoleaf" - -DEVICE = "device" -SERIAL_NO = "serial_no" -NAME = "name" diff --git a/homeassistant/components/nanoleaf/light.py b/homeassistant/components/nanoleaf/light.py index 0a80a3f7d60..f5537d3dc1c 100644 --- a/homeassistant/components/nanoleaf/light.py +++ b/homeassistant/components/nanoleaf/light.py @@ -1,10 +1,8 @@ """Support for Nanoleaf Lights.""" from __future__ import annotations -import logging - from aiohttp import ServerDisconnectedError -from aionanoleaf import Unavailable +from aionanoleaf import Nanoleaf, Unavailable import voluptuous as vol from homeassistant.components.light import ( @@ -32,22 +30,11 @@ from homeassistant.util.color import ( color_temperature_mired_to_kelvin as mired_to_kelvin, ) -from .const import DEVICE, DOMAIN, NAME, SERIAL_NO - -_LOGGER = logging.getLogger(__name__) +from .const import DOMAIN +RESERVED_EFFECTS = ("*Solid*", "*Static*", "*Dynamic*") DEFAULT_NAME = "Nanoleaf" -ICON = "mdi:triangle-outline" - -SUPPORT_NANOLEAF = ( - SUPPORT_BRIGHTNESS - | SUPPORT_COLOR_TEMP - | SUPPORT_EFFECT - | SUPPORT_COLOR - | SUPPORT_TRANSITION -) - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(CONF_HOST): cv.string, @@ -77,94 +64,74 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up the Nanoleaf light.""" - data = hass.data[DOMAIN][entry.entry_id] - async_add_entities([NanoleafLight(data[DEVICE], data[NAME], data[SERIAL_NO])], True) + nanoleaf: Nanoleaf = hass.data[DOMAIN][entry.entry_id] + async_add_entities([NanoleafLight(nanoleaf)]) class NanoleafLight(LightEntity): """Representation of a Nanoleaf Light.""" - def __init__(self, light, name, unique_id): + def __init__(self, nanoleaf: Nanoleaf) -> None: """Initialize an Nanoleaf light.""" - self._unique_id = unique_id - self._available = True - self._brightness = None - self._color_temp = None - self._effect = None - self._effects_list = None - self._light = light - self._name = name - self._hs_color = None - self._state = None - - @property - def available(self): - """Return availability.""" - return self._available + self._nanoleaf = nanoleaf + self._attr_unique_id = self._nanoleaf.serial_no + self._attr_name = self._nanoleaf.name + self._attr_min_mireds = 154 + self._attr_max_mireds = 833 @property def brightness(self): """Return the brightness of the light.""" - if self._brightness is not None: - return int(self._brightness * 2.55) - return None + return int(self._nanoleaf.brightness * 2.55) @property def color_temp(self): """Return the current color temperature.""" - if self._color_temp is not None: - return color_util.color_temperature_kelvin_to_mired(self._color_temp) - return None + return color_util.color_temperature_kelvin_to_mired( + self._nanoleaf.color_temperature + ) @property def effect(self): """Return the current effect.""" - return self._effect + # The API returns the *Solid* effect if the Nanoleaf is in HS or CT mode. + # The effects *Static* and *Dynamic* are not supported by Home Assistant. + # These reserved effects are implicitly set and are not in the effect_list. + # https://forum.nanoleaf.me/docs/openapi#_byoot0bams8f + return ( + None if self._nanoleaf.effect in RESERVED_EFFECTS else self._nanoleaf.effect + ) @property def effect_list(self): """Return the list of supported effects.""" - return self._effects_list - - @property - def min_mireds(self): - """Return the coldest color_temp that this light supports.""" - return 154 - - @property - def max_mireds(self): - """Return the warmest color_temp that this light supports.""" - return 833 - - @property - def unique_id(self): - """Return a unique ID.""" - return self._unique_id - - @property - def name(self): - """Return the display name of this light.""" - return self._name + return self._nanoleaf.effects_list @property def icon(self): """Return the icon to use in the frontend, if any.""" - return ICON + return "mdi:triangle-outline" @property def is_on(self): """Return true if light is on.""" - return self._light.is_on + return self._nanoleaf.is_on @property def hs_color(self): """Return the color in HS.""" - return self._hs_color + return self._nanoleaf.hue, self._nanoleaf.saturation @property def supported_features(self): """Flag supported features.""" - return SUPPORT_NANOLEAF + return ( + SUPPORT_BRIGHTNESS + | SUPPORT_COLOR_TEMP + | SUPPORT_EFFECT + | SUPPORT_COLOR + | SUPPORT_TRANSITION + ) async def async_turn_on(self, **kwargs): """Instruct the light to turn on.""" @@ -176,61 +143,43 @@ class NanoleafLight(LightEntity): if hs_color: hue, saturation = hs_color - await self._light.set_hue(int(hue)) - await self._light.set_saturation(int(saturation)) + await self._nanoleaf.set_hue(int(hue)) + await self._nanoleaf.set_saturation(int(saturation)) if color_temp_mired: - await self._light.set_color_temperature(mired_to_kelvin(color_temp_mired)) + await self._nanoleaf.set_color_temperature( + mired_to_kelvin(color_temp_mired) + ) if transition: if brightness: # tune to the required brightness in n seconds - await self._light.set_brightness( + await self._nanoleaf.set_brightness( int(brightness / 2.55), transition=int(kwargs[ATTR_TRANSITION]) ) else: # If brightness is not specified, assume full brightness - await self._light.set_brightness( - 100, transition=int(kwargs[ATTR_TRANSITION]) - ) + await self._nanoleaf.set_brightness(100, transition=int(transition)) else: # If no transition is occurring, turn on the light - await self._light.turn_on() + await self._nanoleaf.turn_on() if brightness: - await self._light.set_brightness(int(brightness / 2.55)) + await self._nanoleaf.set_brightness(int(brightness / 2.55)) if effect: - if effect not in self._effects_list: + if effect not in self.effect_list: raise ValueError( f"Attempting to apply effect not in the effect list: '{effect}'" ) - await self._light.set_effect(effect) + await self._nanoleaf.set_effect(effect) async def async_turn_off(self, **kwargs): """Instruct the light to turn off.""" transition = kwargs.get(ATTR_TRANSITION) - if transition: - await self._light.set_brightness(0, transition=int(transition)) - else: - await self._light.turn_off() + await self._nanoleaf.turn_off(transition) async def async_update(self) -> None: """Fetch new state data for this light.""" try: - await self._light.get_info() + await self._nanoleaf.get_info() except ServerDisconnectedError: # Retry the request once if the device disconnected - await self._light.get_info() + await self._nanoleaf.get_info() except Unavailable: - self._available = False + self._attr_available = False return - self._available = True - self._brightness = self._light.brightness - self._effects_list = self._light.effects_list - # Nanoleaf api returns non-existent effect named "*Solid*" when light set to solid color. - # This causes various issues with scening (see https://github.com/home-assistant/core/issues/36359). - # Until fixed at the library level, we should ensure the effect exists before saving to light properties - self._effect = ( - self._light.effect if self._light.effect in self._effects_list else None - ) - if self._effect is None: - self._color_temp = self._light.color_temperature - self._hs_color = self._light.hue, self._light.saturation - else: - self._color_temp = None - self._hs_color = None - self._state = self._light.is_on + self._attr_available = True