Home Assistant Unofficial Reference 2024.12.1
util.py
Go to the documentation of this file.
1 """Utils for Magic Home."""
2 
3 from __future__ import annotations
4 
5 from flux_led.aio import AIOWifiLedBulb
6 from flux_led.const import COLOR_MODE_DIM as FLUX_COLOR_MODE_DIM, MultiColorEffects
7 
8 from homeassistant.components.light import ColorMode
9 from homeassistant.util.color import color_hsv_to_RGB, color_RGB_to_hsv
10 
11 from .const import FLUX_COLOR_MODE_TO_HASS, MIN_RGB_BRIGHTNESS
12 
13 
14 def _hass_color_modes(device: AIOWifiLedBulb) -> set[str]:
15  color_modes = device.color_modes
16  if not color_modes:
17  return {ColorMode.ONOFF}
18  return {_flux_color_mode_to_hass(mode, color_modes) for mode in color_modes}
19 
20 
21 def format_as_flux_mac(mac: str | None) -> str | None:
22  """Convert a device registry formatted mac to flux mac."""
23  return None if mac is None else mac.replace(":", "").upper()
24 
25 
26 def _human_readable_option(const_option: str) -> str:
27  return const_option.replace("_", " ").title()
28 
29 
30 def mac_matches_by_one(formatted_mac_1: str, formatted_mac_2: str) -> bool:
31  """Check if a mac address is only one digit off.
32 
33  Some of the devices have two mac addresses which are
34  one off from each other. We need to treat them as the same
35  since its the same device.
36  """
37  mac_int_1 = int(formatted_mac_1.replace(":", ""), 16)
38  mac_int_2 = int(formatted_mac_2.replace(":", ""), 16)
39  return abs(mac_int_1 - mac_int_2) < 2
40 
41 
43  flux_color_mode: str | None, flux_color_modes: set[str]
44 ) -> ColorMode:
45  """Map the flux color mode to Home Assistant color mode."""
46  if flux_color_mode is None:
47  return ColorMode.ONOFF
48  if flux_color_mode == FLUX_COLOR_MODE_DIM:
49  if len(flux_color_modes) > 1:
50  return ColorMode.WHITE
51  return ColorMode.BRIGHTNESS
52  return FLUX_COLOR_MODE_TO_HASS.get(flux_color_mode, ColorMode.ONOFF)
53 
54 
55 def _effect_brightness(brightness: int) -> int:
56  """Convert hass brightness to effect brightness."""
57  return round(brightness / 255 * 100)
58 
59 
60 def _str_to_multi_color_effect(effect_str: str) -> MultiColorEffects:
61  """Convert an multicolor effect string to MultiColorEffects."""
62  for effect in MultiColorEffects:
63  if effect.name.lower() == effect_str:
64  return effect
65  # unreachable due to schema validation
66  raise RuntimeError # pragma: no cover
67 
68 
69 def _is_zero_rgb_brightness(rgb: tuple[int, int, int]) -> bool:
70  """RGB brightness is zero."""
71  return all(byte == 0 for byte in rgb)
72 
73 
74 def _min_rgb_brightness(rgb: tuple[int, int, int]) -> tuple[int, int, int]:
75  """Ensure the RGB value will not turn off the device from a turn on command."""
77  return (MIN_RGB_BRIGHTNESS, MIN_RGB_BRIGHTNESS, MIN_RGB_BRIGHTNESS)
78  return rgb
79 
80 
81 def _min_scaled_rgb_brightness(rgb: tuple[int, int, int]) -> tuple[int, int, int]:
82  """Scale an RGB tuple to minimum brightness."""
83  return color_hsv_to_RGB(*color_RGB_to_hsv(*rgb)[:2], 1)
84 
85 
87  rgbw: tuple[int, int, int, int], current_rgbw: tuple[int, int, int, int]
88 ) -> tuple[int, int, int, int]:
89  """Ensure the RGBW value will not turn off the device from a turn on command.
90 
91  For RGBW, we also need to ensure that there is at least one
92  value in the RGB fields or the device will switch to CCT mode unexpectedly.
93 
94  If the new value being set is all zeros, scale the current
95  color to brightness of 1 so we do not unexpected switch to white
96  """
97  if _is_zero_rgb_brightness(rgbw[:3]):
98  return (*_min_scaled_rgb_brightness(current_rgbw[:3]), rgbw[3])
99  return (*_min_rgb_brightness(rgbw[:3]), rgbw[3])
100 
101 
103  rgbwc: tuple[int, int, int, int, int], current_rgbwc: tuple[int, int, int, int, int]
104 ) -> tuple[int, int, int, int, int]:
105  """Ensure the RGBWC value will not turn off the device from a turn on command.
106 
107  For RGBWC, we also need to ensure that there is at least one
108  value in the RGB fields or the device will switch to CCT mode unexpectedly
109 
110  If the new value being set is all zeros, scale the current
111  color to brightness of 1 so we do not unexpected switch to white
112  """
113  if _is_zero_rgb_brightness(rgbwc[:3]):
114  return (*_min_scaled_rgb_brightness(current_rgbwc[:3]), rgbwc[3], rgbwc[4])
115  return (*_min_rgb_brightness(rgbwc[:3]), rgbwc[3], rgbwc[4])
str _human_readable_option(str const_option)
Definition: util.py:26
bool mac_matches_by_one(str formatted_mac_1, str formatted_mac_2)
Definition: util.py:30
str|None format_as_flux_mac(str|None mac)
Definition: util.py:21
bool _is_zero_rgb_brightness(tuple[int, int, int] rgb)
Definition: util.py:69
int _effect_brightness(int brightness)
Definition: util.py:55
tuple[int, int, int] _min_scaled_rgb_brightness(tuple[int, int, int] rgb)
Definition: util.py:81
ColorMode _flux_color_mode_to_hass(str|None flux_color_mode, set[str] flux_color_modes)
Definition: util.py:44
tuple[int, int, int] _min_rgb_brightness(tuple[int, int, int] rgb)
Definition: util.py:74
tuple[int, int, int, int] _min_rgbw_brightness(tuple[int, int, int, int] rgbw, tuple[int, int, int, int] current_rgbw)
Definition: util.py:88
tuple[int, int, int, int, int] _min_rgbwc_brightness(tuple[int, int, int, int, int] rgbwc, tuple[int, int, int, int, int] current_rgbwc)
Definition: util.py:104
MultiColorEffects _str_to_multi_color_effect(str effect_str)
Definition: util.py:60
set[str] _hass_color_modes(AIOWifiLedBulb device)
Definition: util.py:14
tuple[int, int, int] color_hsv_to_RGB(float iH, float iS, float iV)
Definition: color.py:372
tuple[float, float, float] color_RGB_to_hsv(float iR, float iG, float iB)
Definition: color.py:356