Skip to content

Commit

Permalink
fix: fix deprecated light constants and change to kelvin (#2183)
Browse files Browse the repository at this point in the history
  • Loading branch information
Snuffy2 committed Feb 11, 2024
1 parent 1ed903b commit ab5f05d
Showing 1 changed file with 45 additions and 103 deletions.
148 changes: 45 additions & 103 deletions custom_components/alexa_media/light.py
Expand Up @@ -14,36 +14,18 @@
from alexapy import AlexaAPI, hide_serial
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_COLOR_TEMP,
ATTR_COLOR_TEMP_KELVIN,
ATTR_HS_COLOR,
SUPPORT_BRIGHTNESS,
SUPPORT_COLOR,
SUPPORT_COLOR_TEMP,
ColorMode,
LightEntity,
)
from homeassistant.exceptions import ConfigEntryNotReady

try:
from homeassistant.components.light import (
COLOR_MODE_BRIGHTNESS,
COLOR_MODE_COLOR_TEMP,
COLOR_MODE_HS,
COLOR_MODE_ONOFF,
)
except ImportError:
# Continue to support HA < 2021.4.
COLOR_MODE_BRIGHTNESS = "brightness"
COLOR_MODE_COLOR_TEMP = "color_temp"
COLOR_MODE_HS = "hs"
COLOR_MODE_ONOFF = "onoff"

from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util.color import (
color_hs_to_RGB,
color_hsb_to_RGB,
color_name_to_rgb,
color_RGB_to_hs,
color_temperature_kelvin_to_mired,
)

from . import (
Expand Down Expand Up @@ -131,14 +113,14 @@ async def async_unload_entry(hass, entry) -> bool:
def color_modes(details) -> list:
"""Return list of color modes."""
if details["color"] and details["color_temperature"]:
return [COLOR_MODE_HS, COLOR_MODE_COLOR_TEMP]
return [ColorMode.HS_COLOR, ColorMode.COLOR_TEMP]
if details["color"]:
return [COLOR_MODE_HS]
return [ColorMode.HS_COLOR]
if details["color_temperature"]:
return [COLOR_MODE_COLOR_TEMP]
return [ColorMode.COLOR_TEMP]
if details["brightness"]:
return [COLOR_MODE_BRIGHTNESS]
return [COLOR_MODE_ONOFF]
return [ColorMode.BRIGHTNESS]
return [ColorMode.ONOFF]


class AlexaLight(CoordinatorEntity, LightEntity):
Expand All @@ -150,7 +132,9 @@ def __init__(self, coordinator, login, details):
self.alexa_entity_id = details["id"]
self._name = details["name"]
self._login = login
self._color_modes = color_modes(details)
self._attr_supported_color_modes = color_modes(details)
self._attr_min_color_temp_kelvin = 2200
self._attr_max_color_temp_kelvin = 6500

# Store the requested state from the last call to _set_state
# This is so that no new network call is needed just to get values that are already known
Expand All @@ -160,7 +144,7 @@ def __init__(self, coordinator, login, details):
self._requested_state_at = None # When was state last set in UTC
self._requested_power = None
self._requested_ha_brightness = None
self._requested_mired = None
self._requested_kelvin = None
self._requested_hs = None

@property
Expand All @@ -173,45 +157,19 @@ def unique_id(self):
"""Return unique id."""
return self.alexa_entity_id

@property
def supported_features(self):
"""Return supported features."""
# The HA documentation marks every single feature that Alexa lights can support as deprecated.
# The new alternative is the supported_color_modes and color_mode properties(HA 2021.4)
# This SHOULD just need to return 0 according to the light entity docs.
# Actually doing that causes the UI to remove color controls even in HA 2021.4.
# So, continue to provide a backwards compatible method here until HA is fixed and the min HA version is raised.
if COLOR_MODE_BRIGHTNESS in self._color_modes:
return SUPPORT_BRIGHTNESS
if (
COLOR_MODE_HS in self._color_modes
and COLOR_MODE_COLOR_TEMP in self._color_modes
):
return SUPPORT_BRIGHTNESS | SUPPORT_COLOR | SUPPORT_COLOR_TEMP
if COLOR_MODE_HS in self._color_modes:
return SUPPORT_BRIGHTNESS | SUPPORT_COLOR
if COLOR_MODE_COLOR_TEMP in self._color_modes:
return SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP
return 0

@property
def color_mode(self):
"""Return color mode."""
if (
COLOR_MODE_HS in self._color_modes
and COLOR_MODE_COLOR_TEMP in self._color_modes
ColorMode.HS_COLOR in self._attr_supported_color_modes
and ColorMode.COLOR_TEMP in self._attr_supported_color_modes
):
hs_color = self.hs_color
if hs_color is None or (hs_color[0] == 0 and hs_color[1] == 0):
# (0,0) is white. When white, color temp is the better plan.
return COLOR_MODE_COLOR_TEMP
return COLOR_MODE_HS
return self._color_modes[0]

@property
def supported_color_modes(self):
"""Return supported color modes."""
return self._color_modes
return ColorMode.COLOR_TEMP
return ColorMode.HS_COLOR
return self._attr_supported_color_modes[0]

@property
def is_on(self):
Expand All @@ -234,24 +192,14 @@ def brightness(self):
return alexa_brightness_to_ha(bright)

@property
def min_mireds(self):
"""Return min mireds."""
return 143

@property
def max_mireds(self):
"""Return max mireds."""
return 454

@property
def color_temp(self):
def color_temp_kelvin(self):
"""Return color temperature."""
kelvin = parse_color_temp_from_coordinator(
self.coordinator, self.alexa_entity_id, self._requested_state_at
)
if kelvin is None:
return self._requested_mired
return alexa_kelvin_to_mired(kelvin)
return self._requested_kelvin
return kelvin_to_alexa(kelvin)[0]

@property
def hs_color(self):
Expand All @@ -275,18 +223,18 @@ def assumed_state(self) -> bool:
)
return not last_refresh_success

async def _set_state(self, power_on, brightness=None, mired=None, hs_color=None):
# This is "rounding" on mired to the closest value Alexa is willing to acknowledge the existence of.
async def _set_state(self, power_on, brightness=None, kelvin=None, hs_color=None):
# This is "rounding" on kelvin to the closest value Alexa is willing to acknowledge the existence of.
# The alternative implementation would be to use effects instead.
# That is far more non-standard, and would lock users out of things like the Flux integration.
# The downsides to this approach is that the UI is giving the user a slider
# When the user picks a slider value, the UI will "jump" to the closest possible value.
# This trade-off doesn't feel as bad in practice as it sounds.
adjusted_mired, color_temperature_name = mired_to_alexa(mired)
adjusted_kelvin, color_temperature_name = kelvin_to_alexa(kelvin)
if color_temperature_name is None:
# This is "rounding" on HS color to closest value Alexa supports.
# The alexa color list is short, but covers a pretty broad spectrum.
# Like for mired above, this sounds bad but works ok in practice.
# Like for kelvin above, this sounds bad but works ok in practice.
adjusted_hs, color_name = hs_to_alexa_color(hs_color)
else:
# If a color temperature is being set, it is not possible to also adjust the color.
Expand All @@ -310,13 +258,13 @@ async def _set_state(self, power_on, brightness=None, mired=None, hs_color=None)
self._requested_ha_brightness = (
brightness if brightness is not None else self.brightness
)
self._requested_mired = (
adjusted_mired if adjusted_mired is not None else self.color_temp
self._requested_kelvin = (
adjusted_kelvin if adjusted_kelvin is not None else self.color_temp
)
if adjusted_hs is not None:
self._requested_hs = adjusted_hs
elif adjusted_mired is not None:
# If a mired value was set, it is critical that color is cleared out so that color mode is set properly
elif adjusted_kelvin is not None:
# If a kelvin value was set, it is critical that color is cleared out so that color mode is set properly
self._requested_hs = None
else:
self._requested_hs = self.hs_color
Expand All @@ -328,40 +276,34 @@ async def _set_state(self, power_on, brightness=None, mired=None, hs_color=None)
async def async_turn_on(self, **kwargs):
"""Turn on."""
brightness = None
mired = None
kelvin = None
hs_color = None
if COLOR_MODE_ONOFF not in self._color_modes and ATTR_BRIGHTNESS in kwargs:
if ColorMode.ONOFF not in self._attr_supported_color_modes and ATTR_BRIGHTNESS in kwargs:
brightness = kwargs[ATTR_BRIGHTNESS]
if COLOR_MODE_COLOR_TEMP in self._color_modes and ATTR_COLOR_TEMP in kwargs:
mired = kwargs[ATTR_COLOR_TEMP]
if COLOR_MODE_HS in self._color_modes and ATTR_HS_COLOR in kwargs:
if ColorMode.COLOR_TEMP in self._attr_supported_color_modes and ATTR_COLOR_TEMP_KELVIN in kwargs:
kelvin = kwargs[ATTR_COLOR_TEMP_KELVIN]
if ColorMode.HS_COLOR in self._attr_supported_color_modes and ATTR_HS_COLOR in kwargs:
hs_color = kwargs[ATTR_HS_COLOR]
await self._set_state(True, brightness, mired, hs_color)
await self._set_state(True, brightness, kelvin, hs_color)

async def async_turn_off(self, **kwargs): # pylint:disable=unused-argument
"""Turn off."""
await self._set_state(False)


def mired_to_alexa(mired: Optional[float]) -> tuple[Optional[float], Optional[str]]:
"""Convert a given color temperature in mired to the closest available value that Alexa has support for."""
if mired is None:
def kelvin_to_alexa(kelvin: Optional[float]) -> tuple[Optional[float], Optional[str]]:
"""Convert a given color temperature in kelvin to the closest available value that Alexa has support for."""
if kelvin is None:
return None, None
if mired <= 162.5:
return 143, "cool_white"
if mired <= 216:
return 182, "daylight_white"
if mired <= 310:
return 250, "white"
if mired <= 412:
return 370, "soft_white"
return 454, "warm_white"


def alexa_kelvin_to_mired(kelvin: float) -> float:
"""Convert a value in kelvin to the closest mired value that Alexa has support for."""
raw_mired = color_temperature_kelvin_to_mired(kelvin)
return mired_to_alexa(raw_mired)[0]
if kelvin <= 2400:
return 2200, "warm_white"
if kelvin <= 3200:
return 2700, "soft_white"
if kelvin <= 4400:
return 4000, "white"
if kelvin <= 6000:
return 5400, "daylight_white"
return 6500, "cool_white"


def ha_brightness_to_alexa(ha_brightness: Optional[float]) -> Optional[float]:
Expand Down

0 comments on commit ab5f05d

Please sign in to comment.