core/homeassistant/components/flux_led/util.py

116 lines
4.2 KiB
Python

"""Utils for Magic Home."""
from __future__ import annotations
from flux_led.aio import AIOWifiLedBulb
from flux_led.const import COLOR_MODE_DIM as FLUX_COLOR_MODE_DIM, MultiColorEffects
from homeassistant.components.light import ColorMode
from homeassistant.util.color import color_hsv_to_RGB, color_RGB_to_hsv
from .const import FLUX_COLOR_MODE_TO_HASS, MIN_RGB_BRIGHTNESS
def _hass_color_modes(device: AIOWifiLedBulb) -> set[str]:
color_modes = device.color_modes
if not color_modes:
return {ColorMode.ONOFF}
return {_flux_color_mode_to_hass(mode, color_modes) for mode in color_modes}
def format_as_flux_mac(mac: str | None) -> str | None:
"""Convert a device registry formatted mac to flux mac."""
return None if mac is None else mac.replace(":", "").upper()
def _human_readable_option(const_option: str) -> str:
return const_option.replace("_", " ").title()
def mac_matches_by_one(formatted_mac_1: str, formatted_mac_2: str) -> bool:
"""Check if a mac address is only one digit off.
Some of the devices have two mac addresses which are
one off from each other. We need to treat them as the same
since its the same device.
"""
mac_int_1 = int(formatted_mac_1.replace(":", ""), 16)
mac_int_2 = int(formatted_mac_2.replace(":", ""), 16)
return abs(mac_int_1 - mac_int_2) < 2
def _flux_color_mode_to_hass(
flux_color_mode: str | None, flux_color_modes: set[str]
) -> ColorMode:
"""Map the flux color mode to Home Assistant color mode."""
if flux_color_mode is None:
return ColorMode.ONOFF
if flux_color_mode == FLUX_COLOR_MODE_DIM:
if len(flux_color_modes) > 1:
return ColorMode.WHITE
return ColorMode.BRIGHTNESS
return FLUX_COLOR_MODE_TO_HASS.get(flux_color_mode, ColorMode.ONOFF)
def _effect_brightness(brightness: int) -> int:
"""Convert hass brightness to effect brightness."""
return round(brightness / 255 * 100)
def _str_to_multi_color_effect(effect_str: str) -> MultiColorEffects:
"""Convert an multicolor effect string to MultiColorEffects."""
for effect in MultiColorEffects:
if effect.name.lower() == effect_str:
return effect
# unreachable due to schema validation
raise RuntimeError # pragma: no cover
def _is_zero_rgb_brightness(rgb: tuple[int, int, int]) -> bool:
"""RGB brightness is zero."""
return all(byte == 0 for byte in rgb)
def _min_rgb_brightness(rgb: tuple[int, int, int]) -> tuple[int, int, int]:
"""Ensure the RGB value will not turn off the device from a turn on command."""
if _is_zero_rgb_brightness(rgb):
return (MIN_RGB_BRIGHTNESS, MIN_RGB_BRIGHTNESS, MIN_RGB_BRIGHTNESS)
return rgb
def _min_scaled_rgb_brightness(rgb: tuple[int, int, int]) -> tuple[int, int, int]:
"""Scale an RGB tuple to minimum brightness."""
return color_hsv_to_RGB(*color_RGB_to_hsv(*rgb)[:2], 1)
def _min_rgbw_brightness(
rgbw: tuple[int, int, int, int], current_rgbw: tuple[int, int, int, int]
) -> tuple[int, int, int, int]:
"""Ensure the RGBW value will not turn off the device from a turn on command.
For RGBW, we also need to ensure that there is at least one
value in the RGB fields or the device will switch to CCT mode unexpectedly.
If the new value being set is all zeros, scale the current
color to brightness of 1 so we do not unexpected switch to white
"""
if _is_zero_rgb_brightness(rgbw[:3]):
return (*_min_scaled_rgb_brightness(current_rgbw[:3]), rgbw[3])
return (*_min_rgb_brightness(rgbw[:3]), rgbw[3])
def _min_rgbwc_brightness(
rgbwc: tuple[int, int, int, int, int], current_rgbwc: tuple[int, int, int, int, int]
) -> tuple[int, int, int, int, int]:
"""Ensure the RGBWC value will not turn off the device from a turn on command.
For RGBWC, we also need to ensure that there is at least one
value in the RGB fields or the device will switch to CCT mode unexpectedly
If the new value being set is all zeros, scale the current
color to brightness of 1 so we do not unexpected switch to white
"""
if _is_zero_rgb_brightness(rgbwc[:3]):
return (*_min_scaled_rgb_brightness(current_rgbwc[:3]), rgbwc[3], rgbwc[4])
return (*_min_rgb_brightness(rgbwc[:3]), rgbwc[3], rgbwc[4])